From 67a4016fdf4fa6b255c864e64e5c255751a82b58 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 4 May 2022 15:51:24 -0600 Subject: [PATCH 001/212] maint: Move Go packages into root of repo, adopt go.work (#2524) - Adopts Go workspaces for future compatibility with the Bedrock move into the monorepo - Moves Go packages to the root of the repo in order to fix import paths - Rewrites existing Go import paths - Removes Stackman, since it's not needed anymore Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- proxyd/proxyd/.gitignore | 3 + proxyd/proxyd/CHANGELOG.md | 151 ++++ proxyd/proxyd/Dockerfile | 30 + proxyd/proxyd/Makefile | 21 + proxyd/proxyd/README.md | 26 + proxyd/proxyd/backend.go | 785 ++++++++++++++++++ proxyd/proxyd/cache.go | 165 ++++ proxyd/proxyd/cache_test.go | 622 ++++++++++++++ proxyd/proxyd/cmd/proxyd/main.go | 50 ++ proxyd/proxyd/config.go | 97 +++ proxyd/proxyd/entrypoint.sh | 6 + proxyd/proxyd/errors.go | 7 + proxyd/proxyd/example.config.toml | 96 +++ proxyd/proxyd/go.mod | 22 + proxyd/proxyd/go.sum | 702 ++++++++++++++++ .../integration_tests/batch_timeout_test.go | 42 + .../proxyd/integration_tests/batching_test.go | 141 ++++ .../proxyd/integration_tests/caching_test.go | 215 +++++ .../proxyd/integration_tests/failover_test.go | 242 ++++++ .../integration_tests/max_rpc_conns_test.go | 79 ++ .../integration_tests/mock_backend_test.go | 253 ++++++ .../integration_tests/rate_limit_test.go | 60 ++ .../testdata/batch_timeout.toml | 20 + .../integration_tests/testdata/batching.toml | 19 + .../integration_tests/testdata/caching.toml | 29 + .../integration_tests/testdata/failover.toml | 20 + .../testdata/max_rpc_conns.toml | 19 + .../testdata/out_of_service_interval.toml | 22 + .../testdata/rate_limit.toml | 18 + .../integration_tests/testdata/retries.toml | 18 + .../integration_tests/testdata/whitelist.toml | 17 + proxyd/proxyd/integration_tests/util_test.go | 109 +++ .../integration_tests/validation_test.go | 232 ++++++ proxyd/proxyd/lvc.go | 87 ++ proxyd/proxyd/methods.go | 399 +++++++++ proxyd/proxyd/metrics.go | 280 +++++++ proxyd/proxyd/package.json | 6 + proxyd/proxyd/proxyd.go | 344 ++++++++ proxyd/proxyd/rate_limiter.go | 265 ++++++ proxyd/proxyd/rpc.go | 154 ++++ proxyd/proxyd/rpc_test.go | 76 ++ proxyd/proxyd/server.go | 533 ++++++++++++ proxyd/proxyd/string_set.go | 56 ++ proxyd/proxyd/tls.go | 33 + 44 files changed, 6571 insertions(+) create mode 100644 proxyd/proxyd/.gitignore create mode 100644 proxyd/proxyd/CHANGELOG.md create mode 100644 proxyd/proxyd/Dockerfile create mode 100644 proxyd/proxyd/Makefile create mode 100644 proxyd/proxyd/README.md create mode 100644 proxyd/proxyd/backend.go create mode 100644 proxyd/proxyd/cache.go create mode 100644 proxyd/proxyd/cache_test.go create mode 100644 proxyd/proxyd/cmd/proxyd/main.go create mode 100644 proxyd/proxyd/config.go create mode 100644 proxyd/proxyd/entrypoint.sh create mode 100644 proxyd/proxyd/errors.go create mode 100644 proxyd/proxyd/example.config.toml create mode 100644 proxyd/proxyd/go.mod create mode 100644 proxyd/proxyd/go.sum create mode 100644 proxyd/proxyd/integration_tests/batch_timeout_test.go create mode 100644 proxyd/proxyd/integration_tests/batching_test.go create mode 100644 proxyd/proxyd/integration_tests/caching_test.go create mode 100644 proxyd/proxyd/integration_tests/failover_test.go create mode 100644 proxyd/proxyd/integration_tests/max_rpc_conns_test.go create mode 100644 proxyd/proxyd/integration_tests/mock_backend_test.go create mode 100644 proxyd/proxyd/integration_tests/rate_limit_test.go create mode 100644 proxyd/proxyd/integration_tests/testdata/batch_timeout.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/batching.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/caching.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/failover.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/max_rpc_conns.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/rate_limit.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/retries.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/whitelist.toml create mode 100644 proxyd/proxyd/integration_tests/util_test.go create mode 100644 proxyd/proxyd/integration_tests/validation_test.go create mode 100644 proxyd/proxyd/lvc.go create mode 100644 proxyd/proxyd/methods.go create mode 100644 proxyd/proxyd/metrics.go create mode 100644 proxyd/proxyd/package.json create mode 100644 proxyd/proxyd/proxyd.go create mode 100644 proxyd/proxyd/rate_limiter.go create mode 100644 proxyd/proxyd/rpc.go create mode 100644 proxyd/proxyd/rpc_test.go create mode 100644 proxyd/proxyd/server.go create mode 100644 proxyd/proxyd/string_set.go create mode 100644 proxyd/proxyd/tls.go diff --git a/proxyd/proxyd/.gitignore b/proxyd/proxyd/.gitignore new file mode 100644 index 0000000..65e6a82 --- /dev/null +++ b/proxyd/proxyd/.gitignore @@ -0,0 +1,3 @@ +bin + +config.toml diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md new file mode 100644 index 0000000..677dd13 --- /dev/null +++ b/proxyd/proxyd/CHANGELOG.md @@ -0,0 +1,151 @@ +# @eth-optimism/proxyd + +## 3.8.5 + +### Patch Changes + +- 2a062b11: proxyd: Log ssanitized RPC requests +- d9f058ce: proxyd: Reduced RPC request logging +- a4bfd9e7: proxyd: Limit the number of concurrent RPCs to backends + +## 3.8.4 + +### Patch Changes + +- 08329ba2: proxyd: Record redis cache operation latency +- ae112021: proxyd: Request-scoped context for fast batch RPC short-circuiting + +## 3.8.3 + +### Patch Changes + +- 160f4c3d: Update docker image to use golang 1.18.0 + +## 3.8.2 + +### Patch Changes + +- ae18cea1: Don't hit Redis when the out of service interval is zero + +## 3.8.1 + +### Patch Changes + +- acf7dbd5: Update to go-ethereum v1.10.16 + +## 3.8.0 + +### Minor Changes + +- 527448bb: Handle nil responses better + +## 3.7.0 + +### Minor Changes + +- 3c2926b1: Add debug cache status header to proxyd responses + +## 3.6.0 + +### Minor Changes + +- 096c5f20: proxyd: Allow cached RPCs to be evicted by redis +- 71d64834: Add caching for block-dependent RPCs +- fd2e1523: proxyd: Cache block-dependent RPCs +- 1760613c: Add integration tests and batching + +## 3.5.0 + +### Minor Changes + +- 025a3c0d: Add request/response payload size metrics to proxyd +- daf8db0b: cache immutable RPC responses in proxyd +- 8aa89bf3: Add X-Forwarded-For header when proxying RPCs on proxyd + +## 3.4.1 + +### Patch Changes + +- 415164e1: Force proxyd build + +## 3.4.0 + +### Minor Changes + +- 4b56ed84: Various proxyd fixes + +## 3.3.0 + +### Minor Changes + +- 7b7ffd2e: Allows string RPC ids on proxyd + +## 3.2.0 + +### Minor Changes + +- 73484138: Adds ability to specify env vars in config + +## 3.1.2 + +### Patch Changes + +- 1b79aa62: Release proxyd + +## 3.1.1 + +### Patch Changes + +- b8802054: Trigger release of proxyd +- 34fcb277: Bump proxyd to test release build workflow + +## 3.1.0 + +### Minor Changes + +- da6138fd: Updated metrics, support local rate limiter + +### Patch Changes + +- 6c7f483b: Add support for additional SSL certificates in Docker container + +## 3.0.0 + +### Major Changes + +- abe231bf: Make endpoints match Geth, better logging + +## 2.0.0 + +### Major Changes + +- 6c50098b: Update metrics, support WS +- f827dbda: Brings back the ability to selectively route RPC methods to backend groups + +### Minor Changes + +- 8cc824e5: Updates proxyd to include additional error metrics. +- 9ba4c5e0: Update metrics, support authenticated endpoints +- 78d0f3f0: Put special errors in a dedicated metric, pass along the content-type header + +### Patch Changes + +- 6e6a55b1: Canary release + +## 1.0.2 + +### Patch Changes + +- b9d2fbee: Trigger releases + +## 1.0.1 + +### Patch Changes + +- 893623c9: Trigger patch releases for dockerhub + +## 1.0.0 + +### Major Changes + +- 28aabc41: Initial release of RPC proxy daemon diff --git a/proxyd/proxyd/Dockerfile b/proxyd/proxyd/Dockerfile new file mode 100644 index 0000000..f6ba052 --- /dev/null +++ b/proxyd/proxyd/Dockerfile @@ -0,0 +1,30 @@ +FROM golang:1.18.0-alpine3.15 as builder + +ARG GITCOMMIT=docker +ARG GITDATE=docker +ARG GITVERSION=docker + +RUN apk add make jq git gcc musl-dev linux-headers + +COPY ./proxyd /app + +WORKDIR /app + +RUN make proxyd + +FROM alpine:3.15 + +COPY ./proxyd/entrypoint.sh /bin/entrypoint.sh + +RUN apk update && \ + apk add ca-certificates && \ + chmod +x /bin/entrypoint.sh + +EXPOSE 8080 + +VOLUME /etc/proxyd + +COPY --from=builder /app/bin/proxyd /bin/proxyd + +ENTRYPOINT ["/bin/entrypoint.sh"] +CMD ["/bin/proxyd", "/etc/proxyd/proxyd.toml"] diff --git a/proxyd/proxyd/Makefile b/proxyd/proxyd/Makefile new file mode 100644 index 0000000..263dc61 --- /dev/null +++ b/proxyd/proxyd/Makefile @@ -0,0 +1,21 @@ +LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) +LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) +LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION) +LDFLAGS := -ldflags "$(LDFLAGSSTRING)" + +proxyd: + go build -v $(LDFLAGS) -o ./bin/proxyd ./cmd/proxyd +.PHONY: proxyd + +fmt: + go mod tidy + gofmt -w . +.PHONY: fmt + +test: + go test -race -v ./... +.PHONY: test + +lint: + go vet ./... +.PHONY: test \ No newline at end of file diff --git a/proxyd/proxyd/README.md b/proxyd/proxyd/README.md new file mode 100644 index 0000000..ff019ea --- /dev/null +++ b/proxyd/proxyd/README.md @@ -0,0 +1,26 @@ +# rpc-proxy + +This tool implements `proxyd`, an RPC request router and proxy. It does the following things: + +1. Whitelists RPC methods. +2. Routes RPC methods to groups of backend services. +3. Automatically retries failed backend requests. +4. Provides metrics the measure request latency, error rates, and the like. + +## Usage + +Run `make proxyd` to build the binary. No additional dependencies are necessary. + +To configure `proxyd` for use, you'll need to create a configuration file to define your proxy backends and routing rules. Check out [example.config.toml](./example.config.toml) for how to do this alongside a full list of all options with commentary. + +Once you have a config file, start the daemon via `proxyd .toml`. + +## Metrics + +See `metrics.go` for a list of all available metrics. + +The metrics port is configurable via the `metrics.port` and `metrics.host` keys in the config. + +## Adding Backend SSL Certificates in Docker + +The Docker image runs on Alpine Linux. If you get SSL errors when connecting to a backend within Docker, you may need to add additional certificates to Alpine's certificate store. To do this, bind mount the certificate bundle into a file in `/usr/local/share/ca-certificates`. The `entrypoint.sh` script will then update the store with whatever is in the `ca-certificates` directory prior to starting `proxyd`. \ No newline at end of file diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go new file mode 100644 index 0000000..2e974af --- /dev/null +++ b/proxyd/proxyd/backend.go @@ -0,0 +1,785 @@ +package proxyd + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "math/rand" + "net/http" + "sort" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/gorilla/websocket" + "github.com/prometheus/client_golang/prometheus" + "golang.org/x/sync/semaphore" +) + +const ( + JSONRPCVersion = "2.0" + JSONRPCErrorInternal = -32000 +) + +var ( + ErrParseErr = &RPCErr{ + Code: -32700, + Message: "parse error", + HTTPErrorCode: 400, + } + ErrInternal = &RPCErr{ + Code: JSONRPCErrorInternal, + Message: "internal error", + HTTPErrorCode: 500, + } + ErrMethodNotWhitelisted = &RPCErr{ + Code: JSONRPCErrorInternal - 1, + Message: "rpc method is not whitelisted", + HTTPErrorCode: 403, + } + ErrBackendOffline = &RPCErr{ + Code: JSONRPCErrorInternal - 10, + Message: "backend offline", + HTTPErrorCode: 503, + } + ErrNoBackends = &RPCErr{ + Code: JSONRPCErrorInternal - 11, + Message: "no backends available for method", + HTTPErrorCode: 503, + } + ErrBackendOverCapacity = &RPCErr{ + Code: JSONRPCErrorInternal - 12, + Message: "backend is over capacity", + HTTPErrorCode: 429, + } + ErrBackendBadResponse = &RPCErr{ + Code: JSONRPCErrorInternal - 13, + Message: "backend returned an invalid response", + HTTPErrorCode: 500, + } + ErrTooManyBatchRequests = &RPCErr{ + Code: JSONRPCErrorInternal - 14, + Message: "too many RPC calls in batch request", + } + ErrGatewayTimeout = &RPCErr{ + Code: JSONRPCErrorInternal - 15, + Message: "gateway timeout", + HTTPErrorCode: 504, + } +) + +func ErrInvalidRequest(msg string) *RPCErr { + return &RPCErr{ + Code: -32601, + Message: msg, + HTTPErrorCode: 400, + } +} + +type Backend struct { + Name string + rpcURL string + wsURL string + authUsername string + authPassword string + rateLimiter RateLimiter + client *LimitedHTTPClient + dialer *websocket.Dialer + maxRetries int + maxResponseSize int64 + maxRPS int + maxWSConns int + outOfServiceInterval time.Duration + stripTrailingXFF bool + proxydIP string +} + +type BackendOpt func(b *Backend) + +func WithBasicAuth(username, password string) BackendOpt { + return func(b *Backend) { + b.authUsername = username + b.authPassword = password + } +} + +func WithTimeout(timeout time.Duration) BackendOpt { + return func(b *Backend) { + b.client.Timeout = timeout + } +} + +func WithMaxRetries(retries int) BackendOpt { + return func(b *Backend) { + b.maxRetries = retries + } +} + +func WithMaxResponseSize(size int64) BackendOpt { + return func(b *Backend) { + b.maxResponseSize = size + } +} + +func WithOutOfServiceDuration(interval time.Duration) BackendOpt { + return func(b *Backend) { + b.outOfServiceInterval = interval + } +} + +func WithMaxRPS(maxRPS int) BackendOpt { + return func(b *Backend) { + b.maxRPS = maxRPS + } +} + +func WithMaxWSConns(maxConns int) BackendOpt { + return func(b *Backend) { + b.maxWSConns = maxConns + } +} + +func WithTLSConfig(tlsConfig *tls.Config) BackendOpt { + return func(b *Backend) { + if b.client.Transport == nil { + b.client.Transport = &http.Transport{} + } + b.client.Transport.(*http.Transport).TLSClientConfig = tlsConfig + } +} + +func WithStrippedTrailingXFF() BackendOpt { + return func(b *Backend) { + b.stripTrailingXFF = true + } +} + +func WithProxydIP(ip string) BackendOpt { + return func(b *Backend) { + b.proxydIP = ip + } +} + +func NewBackend( + name string, + rpcURL string, + wsURL string, + rateLimiter RateLimiter, + rpcSemaphore *semaphore.Weighted, + opts ...BackendOpt, +) *Backend { + backend := &Backend{ + Name: name, + rpcURL: rpcURL, + wsURL: wsURL, + rateLimiter: rateLimiter, + maxResponseSize: math.MaxInt64, + client: &LimitedHTTPClient{ + Client: http.Client{Timeout: 5 * time.Second}, + sem: rpcSemaphore, + backendName: name, + }, + dialer: &websocket.Dialer{}, + } + + for _, opt := range opts { + opt(backend) + } + + if !backend.stripTrailingXFF && backend.proxydIP == "" { + log.Warn("proxied requests' XFF header will not contain the proxyd ip address") + } + + return backend +} + +func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { + if !b.Online() { + RecordBatchRPCError(ctx, b.Name, reqs, ErrBackendOffline) + return nil, ErrBackendOffline + } + if b.IsRateLimited() { + RecordBatchRPCError(ctx, b.Name, reqs, ErrBackendOverCapacity) + return nil, ErrBackendOverCapacity + } + + var lastError error + // <= to account for the first attempt not technically being + // a retry + for i := 0; i <= b.maxRetries; i++ { + RecordBatchRPCForward(ctx, b.Name, reqs, RPCRequestSourceHTTP) + metricLabelMethod := reqs[0].Method + if isBatch { + metricLabelMethod = "" + } + timer := prometheus.NewTimer( + rpcBackendRequestDurationSumm.WithLabelValues( + b.Name, + metricLabelMethod, + strconv.FormatBool(isBatch), + ), + ) + + res, err := b.doForward(ctx, reqs, isBatch) + if err != nil { + lastError = err + log.Warn( + "backend request failed, trying again", + "name", b.Name, + "req_id", GetReqID(ctx), + "err", err, + ) + timer.ObserveDuration() + RecordBatchRPCError(ctx, b.Name, reqs, err) + sleepContext(ctx, calcBackoff(i)) + continue + } + timer.ObserveDuration() + + MaybeRecordErrorsInRPCRes(ctx, b.Name, reqs, res) + return res, nil + } + + b.setOffline() + return nil, wrapErr(lastError, "permanent error forwarding request") +} + +func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { + if !b.Online() { + return nil, ErrBackendOffline + } + if b.IsWSSaturated() { + return nil, ErrBackendOverCapacity + } + + backendConn, _, err := b.dialer.Dial(b.wsURL, nil) // nolint:bodyclose + if err != nil { + b.setOffline() + if err := b.rateLimiter.DecBackendWSConns(b.Name); err != nil { + log.Error("error decrementing backend ws conns", "name", b.Name, "err", err) + } + return nil, wrapErr(err, "error dialing backend") + } + + activeBackendWsConnsGauge.WithLabelValues(b.Name).Inc() + return NewWSProxier(b, clientConn, backendConn, methodWhitelist), nil +} + +func (b *Backend) Online() bool { + online, err := b.rateLimiter.IsBackendOnline(b.Name) + if err != nil { + log.Warn( + "error getting backend availability, assuming it is offline", + "name", b.Name, + "err", err, + ) + return false + } + return online +} + +func (b *Backend) IsRateLimited() bool { + if b.maxRPS == 0 { + return false + } + + usedLimit, err := b.rateLimiter.IncBackendRPS(b.Name) + if err != nil { + log.Error( + "error getting backend used rate limit, assuming limit is exhausted", + "name", b.Name, + "err", err, + ) + return true + } + + return b.maxRPS < usedLimit +} + +func (b *Backend) IsWSSaturated() bool { + if b.maxWSConns == 0 { + return false + } + + incremented, err := b.rateLimiter.IncBackendWSConns(b.Name, b.maxWSConns) + if err != nil { + log.Error( + "error getting backend used ws conns, assuming limit is exhausted", + "name", b.Name, + "err", err, + ) + return true + } + + return !incremented +} + +func (b *Backend) setOffline() { + err := b.rateLimiter.SetBackendOffline(b.Name, b.outOfServiceInterval) + if err != nil { + log.Warn( + "error setting backend offline", + "name", b.Name, + "err", err, + ) + } +} + +func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { + body := mustMarshalJSON(rpcReqs) + + httpReq, err := http.NewRequestWithContext(ctx, "POST", b.rpcURL, bytes.NewReader(body)) + if err != nil { + return nil, wrapErr(err, "error creating backend request") + } + + if b.authPassword != "" { + httpReq.SetBasicAuth(b.authUsername, b.authPassword) + } + + xForwardedFor := GetXForwardedFor(ctx) + if b.stripTrailingXFF { + ipList := strings.Split(xForwardedFor, ", ") + if len(ipList) > 0 { + xForwardedFor = ipList[0] + } + } else if b.proxydIP != "" { + xForwardedFor = fmt.Sprintf("%s, %s", xForwardedFor, b.proxydIP) + } + + httpReq.Header.Set("content-type", "application/json") + httpReq.Header.Set("X-Forwarded-For", xForwardedFor) + + httpRes, err := b.client.DoLimited(httpReq) + if err != nil { + return nil, wrapErr(err, "error in backend request") + } + + metricLabelMethod := rpcReqs[0].Method + if isBatch { + metricLabelMethod = "" + } + rpcBackendHTTPResponseCodesTotal.WithLabelValues( + GetAuthCtx(ctx), + b.Name, + metricLabelMethod, + strconv.Itoa(httpRes.StatusCode), + strconv.FormatBool(isBatch), + ).Inc() + + // Alchemy returns a 400 on bad JSONs, so handle that case + if httpRes.StatusCode != 200 && httpRes.StatusCode != 400 { + return nil, fmt.Errorf("response code %d", httpRes.StatusCode) + } + + defer httpRes.Body.Close() + resB, err := ioutil.ReadAll(io.LimitReader(httpRes.Body, b.maxResponseSize)) + if err != nil { + return nil, wrapErr(err, "error reading response body") + } + + var res []*RPCRes + if err := json.Unmarshal(resB, &res); err != nil { + return nil, ErrBackendBadResponse + } + + // Alas! Certain node providers (Infura) always return a single JSON object for some types of errors + if len(rpcReqs) != len(res) { + return nil, ErrBackendBadResponse + } + + // capture the HTTP status code in the response. this will only + // ever be 400 given the status check on line 318 above. + if httpRes.StatusCode != 200 { + for _, res := range res { + res.Error.HTTPErrorCode = httpRes.StatusCode + } + } + + sortBatchRPCResponse(rpcReqs, res) + return res, nil +} + +// sortBatchRPCResponse sorts the RPCRes slice according to the position of its corresponding ID in the RPCReq slice +func sortBatchRPCResponse(req []*RPCReq, res []*RPCRes) { + pos := make(map[string]int, len(req)) + for i, r := range req { + key := string(r.ID) + if _, ok := pos[key]; ok { + panic("bug! detected requests with duplicate IDs") + } + pos[key] = i + } + + sort.Slice(res, func(i, j int) bool { + l := res[i].ID + r := res[j].ID + return pos[string(l)] < pos[string(r)] + }) +} + +type BackendGroup struct { + Name string + Backends []*Backend +} + +func (b *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { + if len(rpcReqs) == 0 { + return nil, nil + } + + rpcRequestsTotal.Inc() + + for _, back := range b.Backends { + res, err := back.Forward(ctx, rpcReqs, isBatch) + if errors.Is(err, ErrMethodNotWhitelisted) { + return nil, err + } + if errors.Is(err, ErrBackendOffline) { + log.Warn( + "skipping offline backend", + "name", back.Name, + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + continue + } + if errors.Is(err, ErrBackendOverCapacity) { + log.Warn( + "skipping over-capacity backend", + "name", back.Name, + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + continue + } + if err != nil { + log.Error( + "error forwarding request to backend", + "name", back.Name, + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + "err", err, + ) + continue + } + return res, nil + } + + RecordUnserviceableRequest(ctx, RPCRequestSourceHTTP) + return nil, ErrNoBackends +} + +func (b *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { + for _, back := range b.Backends { + proxier, err := back.ProxyWS(clientConn, methodWhitelist) + if errors.Is(err, ErrBackendOffline) { + log.Warn( + "skipping offline backend", + "name", back.Name, + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + ) + continue + } + if errors.Is(err, ErrBackendOverCapacity) { + log.Warn( + "skipping over-capacity backend", + "name", back.Name, + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + ) + continue + } + if err != nil { + log.Warn( + "error dialing ws backend", + "name", back.Name, + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + "err", err, + ) + continue + } + return proxier, nil + } + + return nil, ErrNoBackends +} + +func calcBackoff(i int) time.Duration { + jitter := float64(rand.Int63n(250)) + ms := math.Min(math.Pow(2, float64(i))*1000+jitter, 3000) + return time.Duration(ms) * time.Millisecond +} + +type WSProxier struct { + backend *Backend + clientConn *websocket.Conn + backendConn *websocket.Conn + methodWhitelist *StringSet +} + +func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, methodWhitelist *StringSet) *WSProxier { + return &WSProxier{ + backend: backend, + clientConn: clientConn, + backendConn: backendConn, + methodWhitelist: methodWhitelist, + } +} + +func (w *WSProxier) Proxy(ctx context.Context) error { + errC := make(chan error, 2) + go w.clientPump(ctx, errC) + go w.backendPump(ctx, errC) + err := <-errC + w.close() + return err +} + +func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { + for { + outConn := w.backendConn + // Block until we get a message. + msgType, msg, err := w.clientConn.ReadMessage() + if err != nil { + errC <- err + if err := outConn.WriteMessage(websocket.CloseMessage, formatWSError(err)); err != nil { + log.Error("error writing backendConn message", "err", err) + } + return + } + + RecordWSMessage(ctx, w.backend.Name, SourceClient) + + // Route control messages to the backend. These don't + // count towards the total RPC requests count. + if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage { + err := outConn.WriteMessage(msgType, msg) + if err != nil { + errC <- err + return + } + continue + } + + rpcRequestsTotal.Inc() + + // Don't bother sending invalid requests to the backend, + // just handle them here. + req, err := w.prepareClientMsg(msg) + if err != nil { + var id json.RawMessage + method := MethodUnknown + if req != nil { + id = req.ID + method = req.Method + } + log.Info( + "error preparing client message", + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + "err", err, + ) + outConn = w.clientConn + msg = mustMarshalJSON(NewRPCErrorRes(id, err)) + RecordRPCError(ctx, BackendProxyd, method, err) + } else { + RecordRPCForward(ctx, w.backend.Name, req.Method, RPCRequestSourceWS) + log.Info( + "forwarded WS message to backend", + "method", req.Method, + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + } + + err = outConn.WriteMessage(msgType, msg) + if err != nil { + errC <- err + return + } + } +} + +func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { + for { + // Block until we get a message. + msgType, msg, err := w.backendConn.ReadMessage() + if err != nil { + errC <- err + if err := w.clientConn.WriteMessage(websocket.CloseMessage, formatWSError(err)); err != nil { + log.Error("error writing clientConn message", "err", err) + } + return + } + + RecordWSMessage(ctx, w.backend.Name, SourceBackend) + + // Route control messages directly to the client. + if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage { + err := w.clientConn.WriteMessage(msgType, msg) + if err != nil { + errC <- err + return + } + continue + } + + res, err := w.parseBackendMsg(msg) + if err != nil { + var id json.RawMessage + if res != nil { + id = res.ID + } + msg = mustMarshalJSON(NewRPCErrorRes(id, err)) + } + if res.IsError() { + log.Info( + "backend responded with RPC error", + "code", res.Error.Code, + "msg", res.Error.Message, + "source", "ws", + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + RecordRPCError(ctx, w.backend.Name, MethodUnknown, res.Error) + } else { + log.Info( + "forwarded WS message to client", + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + } + + err = w.clientConn.WriteMessage(msgType, msg) + if err != nil { + errC <- err + return + } + } +} + +func (w *WSProxier) close() { + w.clientConn.Close() + w.backendConn.Close() + if err := w.backend.rateLimiter.DecBackendWSConns(w.backend.Name); err != nil { + log.Error("error decrementing backend ws conns", "name", w.backend.Name, "err", err) + } + activeBackendWsConnsGauge.WithLabelValues(w.backend.Name).Dec() +} + +func (w *WSProxier) prepareClientMsg(msg []byte) (*RPCReq, error) { + req, err := ParseRPCReq(msg) + if err != nil { + return nil, err + } + + if !w.methodWhitelist.Has(req.Method) { + return req, ErrMethodNotWhitelisted + } + + if w.backend.IsRateLimited() { + return req, ErrBackendOverCapacity + } + + return req, nil +} + +func (w *WSProxier) parseBackendMsg(msg []byte) (*RPCRes, error) { + res, err := ParseRPCRes(bytes.NewReader(msg)) + if err != nil { + log.Warn("error parsing RPC response", "source", "ws", "err", err) + return res, ErrBackendBadResponse + } + return res, nil +} + +func mustMarshalJSON(in interface{}) []byte { + out, err := json.Marshal(in) + if err != nil { + panic(err) + } + return out +} + +func formatWSError(err error) []byte { + m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err)) + if e, ok := err.(*websocket.CloseError); ok { + if e.Code != websocket.CloseNoStatusReceived { + m = websocket.FormatCloseMessage(e.Code, e.Text) + } + } + return m +} + +func sleepContext(ctx context.Context, duration time.Duration) { + select { + case <-ctx.Done(): + case <-time.After(duration): + } +} + +type LimitedHTTPClient struct { + http.Client + sem *semaphore.Weighted + backendName string +} + +func (c *LimitedHTTPClient) DoLimited(req *http.Request) (*http.Response, error) { + if err := c.sem.Acquire(req.Context(), 1); err != nil { + tooManyRequestErrorsTotal.WithLabelValues(c.backendName).Inc() + return nil, wrapErr(err, "too many requests") + } + defer c.sem.Release(1) + return c.Do(req) +} + +func RecordBatchRPCError(ctx context.Context, backendName string, reqs []*RPCReq, err error) { + for _, req := range reqs { + RecordRPCError(ctx, backendName, req.Method, err) + } +} + +func MaybeRecordErrorsInRPCRes(ctx context.Context, backendName string, reqs []*RPCReq, resBatch []*RPCRes) { + log.Info("forwarded RPC request", + "backend", backendName, + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + "batch_size", len(reqs), + ) + + var lastError *RPCErr + for i, res := range resBatch { + if res.IsError() { + lastError = res.Error + RecordRPCError(ctx, backendName, reqs[i].Method, res.Error) + } + } + + if lastError != nil { + log.Info( + "backend responded with RPC error", + "backend", backendName, + "last_error_code", lastError.Code, + "last_error_msg", lastError.Message, + "req_id", GetReqID(ctx), + "source", "rpc", + "auth", GetAuthCtx(ctx), + ) + } +} + +func RecordBatchRPCForward(ctx context.Context, backendName string, reqs []*RPCReq, source string) { + for _, req := range reqs { + RecordRPCForward(ctx, backendName, req.Method, source) + } +} diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go new file mode 100644 index 0000000..69dbb0b --- /dev/null +++ b/proxyd/proxyd/cache.go @@ -0,0 +1,165 @@ +package proxyd + +import ( + "context" + "time" + + "github.com/go-redis/redis/v8" + "github.com/golang/snappy" + lru "github.com/hashicorp/golang-lru" +) + +type Cache interface { + Get(ctx context.Context, key string) (string, error) + Put(ctx context.Context, key string, value string) error +} + +const ( + // assuming an average RPCRes size of 3 KB + memoryCacheLimit = 4096 + // Set a large ttl to avoid expirations. However, a ttl must be set for volatile-lru to take effect. + redisTTL = 30 * 7 * 24 * time.Hour +) + +type cache struct { + lru *lru.Cache +} + +func newMemoryCache() *cache { + rep, _ := lru.New(memoryCacheLimit) + return &cache{rep} +} + +func (c *cache) Get(ctx context.Context, key string) (string, error) { + if val, ok := c.lru.Get(key); ok { + return val.(string), nil + } + return "", nil +} + +func (c *cache) Put(ctx context.Context, key string, value string) error { + c.lru.Add(key, value) + return nil +} + +type redisCache struct { + rdb *redis.Client +} + +func newRedisCache(url string) (*redisCache, error) { + opts, err := redis.ParseURL(url) + if err != nil { + return nil, err + } + rdb := redis.NewClient(opts) + if err := rdb.Ping(context.Background()).Err(); err != nil { + return nil, wrapErr(err, "error connecting to redis") + } + return &redisCache{rdb}, nil +} + +func (c *redisCache) Get(ctx context.Context, key string) (string, error) { + start := time.Now() + val, err := c.rdb.Get(ctx, key).Result() + redisCacheDurationSumm.WithLabelValues("GET").Observe(float64(time.Since(start).Milliseconds())) + + if err == redis.Nil { + return "", nil + } else if err != nil { + RecordRedisError("CacheGet") + return "", err + } + return val, nil +} + +func (c *redisCache) Put(ctx context.Context, key string, value string) error { + start := time.Now() + err := c.rdb.SetEX(ctx, key, value, redisTTL).Err() + redisCacheDurationSumm.WithLabelValues("SETEX").Observe(float64(time.Since(start).Milliseconds())) + + if err != nil { + RecordRedisError("CacheSet") + } + return err +} + +type cacheWithCompression struct { + cache Cache +} + +func newCacheWithCompression(cache Cache) *cacheWithCompression { + return &cacheWithCompression{cache} +} + +func (c *cacheWithCompression) Get(ctx context.Context, key string) (string, error) { + encodedVal, err := c.cache.Get(ctx, key) + if err != nil { + return "", err + } + if encodedVal == "" { + return "", nil + } + val, err := snappy.Decode(nil, []byte(encodedVal)) + if err != nil { + return "", err + } + return string(val), nil +} + +func (c *cacheWithCompression) Put(ctx context.Context, key string, value string) error { + encodedVal := snappy.Encode(nil, []byte(value)) + return c.cache.Put(ctx, key, string(encodedVal)) +} + +type GetLatestBlockNumFn func(ctx context.Context) (uint64, error) +type GetLatestGasPriceFn func(ctx context.Context) (uint64, error) + +type RPCCache interface { + GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) + PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error +} + +type rpcCache struct { + cache Cache + handlers map[string]RPCMethodHandler +} + +func newRPCCache(cache Cache, getLatestBlockNumFn GetLatestBlockNumFn, getLatestGasPriceFn GetLatestGasPriceFn, numBlockConfirmations int) RPCCache { + handlers := map[string]RPCMethodHandler{ + "eth_chainId": &StaticMethodHandler{}, + "net_version": &StaticMethodHandler{}, + "eth_getBlockByNumber": &EthGetBlockByNumberMethodHandler{cache, getLatestBlockNumFn, numBlockConfirmations}, + "eth_getBlockRange": &EthGetBlockRangeMethodHandler{cache, getLatestBlockNumFn, numBlockConfirmations}, + "eth_blockNumber": &EthBlockNumberMethodHandler{getLatestBlockNumFn}, + "eth_gasPrice": &EthGasPriceMethodHandler{getLatestGasPriceFn}, + "eth_call": &EthCallMethodHandler{cache, getLatestBlockNumFn, numBlockConfirmations}, + } + return &rpcCache{ + cache: cache, + handlers: handlers, + } +} + +func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) { + handler := c.handlers[req.Method] + if handler == nil { + return nil, nil + } + res, err := handler.GetRPCMethod(ctx, req) + if res != nil { + if res == nil { + RecordCacheMiss(req.Method) + } else { + RecordCacheHit(req.Method) + } + } + return res, err +} + +func (c *rpcCache) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error { + handler := c.handlers[req.Method] + if handler == nil { + return nil + } + return handler.PutRPCMethod(ctx, req, res) +} diff --git a/proxyd/proxyd/cache_test.go b/proxyd/proxyd/cache_test.go new file mode 100644 index 0000000..11c277b --- /dev/null +++ b/proxyd/proxyd/cache_test.go @@ -0,0 +1,622 @@ +package proxyd + +import ( + "context" + "math" + "strconv" + "testing" + + "github.com/stretchr/testify/require" +) + +const numBlockConfirmations = 10 + +func TestRPCCacheImmutableRPCs(t *testing.T) { + const blockHead = math.MaxUint64 + ctx := context.Background() + + getBlockNum := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + cache := newRPCCache(newMemoryCache(), getBlockNum, nil, numBlockConfirmations) + ID := []byte(strconv.Itoa(1)) + + rpcs := []struct { + req *RPCReq + res *RPCRes + name string + }{ + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_chainId", + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: "0xff", + ID: ID, + }, + name: "eth_chainId", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "net_version", + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: "9999", + ID: ID, + }, + name: "net_version", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["0x1", false]`), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `{"difficulty": "0x1", "number": "0x1"}`, + ID: ID, + }, + name: "eth_getBlockByNumber", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["earliest", false]`), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `{"difficulty": "0x1", "number": "0x1"}`, + ID: ID, + }, + name: "eth_getBlockByNumber earliest", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockRange", + Params: []byte(`["0x1", "0x2", false]`), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + ID: ID, + }, + name: "eth_getBlockRange", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockRange", + Params: []byte(`["earliest", "0x2", false]`), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + ID: ID, + }, + name: "eth_getBlockRange earliest", + }, + } + + for _, rpc := range rpcs { + t.Run(rpc.name, func(t *testing.T) { + err := cache.PutRPC(ctx, rpc.req, rpc.res) + require.NoError(t, err) + + cachedRes, err := cache.GetRPC(ctx, rpc.req) + require.NoError(t, err) + require.Equal(t, rpc.res, cachedRes) + }) + } +} + +func TestRPCCacheBlockNumber(t *testing.T) { + var blockHead uint64 = 0x1000 + var gasPrice uint64 = 0x100 + ctx := context.Background() + ID := []byte(strconv.Itoa(1)) + + getGasPrice := func(ctx context.Context) (uint64, error) { + return gasPrice, nil + } + getBlockNum := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + cache := newRPCCache(newMemoryCache(), getBlockNum, getGasPrice, numBlockConfirmations) + + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_blockNumber", + ID: ID, + } + res := &RPCRes{ + JSONRPC: "2.0", + Result: `0x1000`, + ID: ID, + } + + err := cache.PutRPC(ctx, req, res) + require.NoError(t, err) + + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Equal(t, res, cachedRes) + + blockHead = 0x1001 + cachedRes, err = cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Equal(t, &RPCRes{JSONRPC: "2.0", Result: `0x1001`, ID: ID}, cachedRes) +} + +func TestRPCCacheGasPrice(t *testing.T) { + var blockHead uint64 = 0x1000 + var gasPrice uint64 = 0x100 + ctx := context.Background() + ID := []byte(strconv.Itoa(1)) + + getGasPrice := func(ctx context.Context) (uint64, error) { + return gasPrice, nil + } + getBlockNum := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + cache := newRPCCache(newMemoryCache(), getBlockNum, getGasPrice, numBlockConfirmations) + + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_gasPrice", + ID: ID, + } + res := &RPCRes{ + JSONRPC: "2.0", + Result: `0x100`, + ID: ID, + } + + err := cache.PutRPC(ctx, req, res) + require.NoError(t, err) + + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Equal(t, res, cachedRes) + + gasPrice = 0x101 + cachedRes, err = cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Equal(t, &RPCRes{JSONRPC: "2.0", Result: `0x101`, ID: ID}, cachedRes) +} + +func TestRPCCacheUnsupportedMethod(t *testing.T) { + const blockHead = math.MaxUint64 + ctx := context.Background() + + fn := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) + ID := []byte(strconv.Itoa(1)) + + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_syncing", + ID: ID, + } + res := &RPCRes{ + JSONRPC: "2.0", + Result: false, + ID: ID, + } + + err := cache.PutRPC(ctx, req, res) + require.NoError(t, err) + + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Nil(t, cachedRes) +} + +func TestRPCCacheEthGetBlockByNumber(t *testing.T) { + ctx := context.Background() + + var blockHead uint64 + fn := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + makeCache := func() RPCCache { return newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) } + ID := []byte(strconv.Itoa(1)) + + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["0xa", false]`), + ID: ID, + } + res := &RPCRes{ + JSONRPC: "2.0", + Result: `{"difficulty": "0x1", "number": "0x1"}`, + ID: ID, + } + req2 := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["0xb", false]`), + ID: ID, + } + res2 := &RPCRes{ + JSONRPC: "2.0", + Result: `{"difficulty": "0x2", "number": "0x2"}`, + ID: ID, + } + + t.Run("set multiple finalized blocks", func(t *testing.T) { + blockHead = 100 + cache := makeCache() + require.NoError(t, cache.PutRPC(ctx, req, res)) + require.NoError(t, cache.PutRPC(ctx, req2, res2)) + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Equal(t, res, cachedRes) + cachedRes, err = cache.GetRPC(ctx, req2) + require.NoError(t, err) + require.Equal(t, res2, cachedRes) + }) + + t.Run("unconfirmed block", func(t *testing.T) { + blockHead = 0xc + cache := makeCache() + require.NoError(t, cache.PutRPC(ctx, req, res)) + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Nil(t, cachedRes) + }) +} + +func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) { + ctx := context.Background() + + var blockHead uint64 = 2 + fn := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) + ID := []byte(strconv.Itoa(1)) + + rpcs := []struct { + req *RPCReq + res *RPCRes + name string + }{ + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["latest", false]`), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `{"difficulty": "0x1", "number": "0x1"}`, + ID: ID, + }, + name: "latest block", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["pending", false]`), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `{"difficulty": "0x1", "number": "0x1"}`, + ID: ID, + }, + name: "pending block", + }, + } + + for _, rpc := range rpcs { + t.Run(rpc.name, func(t *testing.T) { + err := cache.PutRPC(ctx, rpc.req, rpc.res) + require.NoError(t, err) + + cachedRes, err := cache.GetRPC(ctx, rpc.req) + require.NoError(t, err) + require.Nil(t, cachedRes) + }) + } +} + +func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) { + ctx := context.Background() + + const blockHead = math.MaxUint64 + fn := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) + ID := []byte(strconv.Itoa(1)) + + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["0x1"]`), // missing required boolean param + ID: ID, + } + res := &RPCRes{ + JSONRPC: "2.0", + Result: `{"difficulty": "0x1", "number": "0x1"}`, + ID: ID, + } + + err := cache.PutRPC(ctx, req, res) + require.Error(t, err) + + cachedRes, err := cache.GetRPC(ctx, req) + require.Error(t, err) + require.Nil(t, cachedRes) +} + +func TestRPCCacheEthGetBlockRange(t *testing.T) { + ctx := context.Background() + + var blockHead uint64 + fn := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + makeCache := func() RPCCache { return newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) } + ID := []byte(strconv.Itoa(1)) + + t.Run("finalized block", func(t *testing.T) { + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockRange", + Params: []byte(`["0x1", "0x10", false]`), + ID: ID, + } + res := &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x10"}]`, + ID: ID, + } + blockHead = 0x1000 + cache := makeCache() + require.NoError(t, cache.PutRPC(ctx, req, res)) + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Equal(t, res, cachedRes) + }) + + t.Run("unconfirmed block", func(t *testing.T) { + cache := makeCache() + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockRange", + Params: []byte(`["0x1", "0x1000", false]`), + ID: ID, + } + res := &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + ID: ID, + } + require.NoError(t, cache.PutRPC(ctx, req, res)) + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Nil(t, cachedRes) + }) +} + +func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) { + ctx := context.Background() + + var blockHead uint64 = 0x1000 + fn := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) + ID := []byte(strconv.Itoa(1)) + + rpcs := []struct { + req *RPCReq + res *RPCRes + name string + }{ + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockRange", + Params: []byte(`["0x1", "latest", false]`), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + ID: ID, + }, + name: "latest block", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockRange", + Params: []byte(`["0x1", "pending", false]`), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + ID: ID, + }, + name: "pending block", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockRange", + Params: []byte(`["latest", "0x1000", false]`), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + ID: ID, + }, + name: "latest block 2", + }, + } + + for _, rpc := range rpcs { + t.Run(rpc.name, func(t *testing.T) { + err := cache.PutRPC(ctx, rpc.req, rpc.res) + require.NoError(t, err) + + cachedRes, err := cache.GetRPC(ctx, rpc.req) + require.NoError(t, err) + require.Nil(t, cachedRes) + }) + } +} + +func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) { + ctx := context.Background() + + const blockHead = math.MaxUint64 + fn := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) + ID := []byte(strconv.Itoa(1)) + + rpcs := []struct { + req *RPCReq + res *RPCRes + name string + }{ + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockRange", + Params: []byte(`["0x1", "0x2"]`), // missing required boolean param + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + ID: ID, + }, + name: "missing boolean param", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockRange", + Params: []byte(`["abc", "0x2", true]`), // invalid block hex + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + ID: ID, + }, + name: "invalid block hex", + }, + } + + for _, rpc := range rpcs { + t.Run(rpc.name, func(t *testing.T) { + err := cache.PutRPC(ctx, rpc.req, rpc.res) + require.Error(t, err) + + cachedRes, err := cache.GetRPC(ctx, rpc.req) + require.Error(t, err) + require.Nil(t, cachedRes) + }) + } +} + +func TestRPCCacheEthCall(t *testing.T) { + ctx := context.Background() + + var blockHead uint64 + fn := func(ctx context.Context) (uint64, error) { + return blockHead, nil + } + + makeCache := func() RPCCache { return newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) } + ID := []byte(strconv.Itoa(1)) + + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_call", + Params: []byte(`[{"to": "0xDEADBEEF", "data": "0x1"}, "0x10"]`), + ID: ID, + } + res := &RPCRes{ + JSONRPC: "2.0", + Result: `0x0`, + ID: ID, + } + + t.Run("finalized block", func(t *testing.T) { + blockHead = 0x100 + cache := makeCache() + err := cache.PutRPC(ctx, req, res) + require.NoError(t, err) + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Equal(t, res, cachedRes) + }) + + t.Run("unconfirmed block", func(t *testing.T) { + blockHead = 0x10 + cache := makeCache() + require.NoError(t, cache.PutRPC(ctx, req, res)) + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Nil(t, cachedRes) + }) + + t.Run("latest block", func(t *testing.T) { + blockHead = 0x100 + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_call", + Params: []byte(`[{"to": "0xDEADBEEF", "data": "0x1"}, "latest"]`), + ID: ID, + } + cache := makeCache() + require.NoError(t, cache.PutRPC(ctx, req, res)) + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Nil(t, cachedRes) + }) + + t.Run("pending block", func(t *testing.T) { + blockHead = 0x100 + req := &RPCReq{ + JSONRPC: "2.0", + Method: "eth_call", + Params: []byte(`[{"to": "0xDEADBEEF", "data": "0x1"}, "pending"]`), + ID: ID, + } + cache := makeCache() + require.NoError(t, cache.PutRPC(ctx, req, res)) + cachedRes, err := cache.GetRPC(ctx, req) + require.NoError(t, err) + require.Nil(t, cachedRes) + }) +} diff --git a/proxyd/proxyd/cmd/proxyd/main.go b/proxyd/proxyd/cmd/proxyd/main.go new file mode 100644 index 0000000..db3828b --- /dev/null +++ b/proxyd/proxyd/cmd/proxyd/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "os" + "os/signal" + "syscall" + + "github.com/BurntSushi/toml" + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/ethereum/go-ethereum/log" +) + +var ( + GitVersion = "" + GitCommit = "" + GitDate = "" +) + +func main() { + // Set up logger with a default INFO level in case we fail to parse flags. + // Otherwise the final critical log won't show what the parsing error was. + log.Root().SetHandler( + log.LvlFilterHandler( + log.LvlInfo, + log.StreamHandler(os.Stdout, log.JSONFormat()), + ), + ) + + log.Info("starting proxyd", "version", GitVersion, "commit", GitCommit, "date", GitDate) + + if len(os.Args) < 2 { + log.Crit("must specify a config file on the command line") + } + + config := new(proxyd.Config) + if _, err := toml.DecodeFile(os.Args[1], config); err != nil { + log.Crit("error reading config file", "err", err) + } + + shutdown, err := proxyd.Start(config) + if err != nil { + log.Crit("error starting proxyd", "err", err) + } + + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + recvSig := <-sig + log.Info("caught signal, shutting down", "signal", recvSig) + shutdown() +} diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go new file mode 100644 index 0000000..26929d9 --- /dev/null +++ b/proxyd/proxyd/config.go @@ -0,0 +1,97 @@ +package proxyd + +import ( + "fmt" + "os" + "strings" +) + +type ServerConfig struct { + RPCHost string `toml:"rpc_host"` + RPCPort int `toml:"rpc_port"` + WSHost string `toml:"ws_host"` + WSPort int `toml:"ws_port"` + MaxBodySizeBytes int64 `toml:"max_body_size_bytes"` + MaxConcurrentRPCs int64 `toml:"max_concurrent_rpcs"` + + // TimeoutSeconds specifies the maximum time spent serving an HTTP request. Note that isn't used for websocket connections + TimeoutSeconds int `toml:"timeout_seconds"` + + MaxUpstreamBatchSize int `toml:"max_upstream_batch_size"` +} + +type CacheConfig struct { + Enabled bool `toml:"enabled"` + BlockSyncRPCURL string `toml:"block_sync_rpc_url"` + NumBlockConfirmations int `toml:"num_block_confirmations"` +} + +type RedisConfig struct { + URL string `toml:"url"` +} + +type MetricsConfig struct { + Enabled bool `toml:"enabled"` + Host string `toml:"host"` + Port int `toml:"port"` +} + +type BackendOptions struct { + ResponseTimeoutSeconds int `toml:"response_timeout_seconds"` + MaxResponseSizeBytes int64 `toml:"max_response_size_bytes"` + MaxRetries int `toml:"max_retries"` + OutOfServiceSeconds int `toml:"out_of_service_seconds"` +} + +type BackendConfig struct { + Username string `toml:"username"` + Password string `toml:"password"` + RPCURL string `toml:"rpc_url"` + WSURL string `toml:"ws_url"` + MaxRPS int `toml:"max_rps"` + MaxWSConns int `toml:"max_ws_conns"` + CAFile string `toml:"ca_file"` + ClientCertFile string `toml:"client_cert_file"` + ClientKeyFile string `toml:"client_key_file"` + StripTrailingXFF bool `toml:"strip_trailing_xff"` +} + +type BackendsConfig map[string]*BackendConfig + +type BackendGroupConfig struct { + Backends []string `toml:"backends"` +} + +type BackendGroupsConfig map[string]*BackendGroupConfig + +type MethodMappingsConfig map[string]string + +type Config struct { + WSBackendGroup string `toml:"ws_backend_group"` + Server ServerConfig `toml:"server"` + Cache CacheConfig `toml:"cache"` + Redis RedisConfig `toml:"redis"` + Metrics MetricsConfig `toml:"metrics"` + BackendOptions BackendOptions `toml:"backend"` + Backends BackendsConfig `toml:"backends"` + Authentication map[string]string `toml:"authentication"` + BackendGroups BackendGroupsConfig `toml:"backend_groups"` + RPCMethodMappings map[string]string `toml:"rpc_method_mappings"` + WSMethodWhitelist []string `toml:"ws_method_whitelist"` +} + +func ReadFromEnvOrConfig(value string) (string, error) { + if strings.HasPrefix(value, "$") { + envValue := os.Getenv(strings.TrimPrefix(value, "$")) + if envValue == "" { + return "", fmt.Errorf("config env var %s not found", value) + } + return envValue, nil + } + + if strings.HasPrefix(value, "\\") { + return strings.TrimPrefix(value, "\\"), nil + } + + return value, nil +} diff --git a/proxyd/proxyd/entrypoint.sh b/proxyd/proxyd/entrypoint.sh new file mode 100644 index 0000000..ef83fa8 --- /dev/null +++ b/proxyd/proxyd/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/proxyd/proxyd/errors.go b/proxyd/proxyd/errors.go new file mode 100644 index 0000000..97a7ebc --- /dev/null +++ b/proxyd/proxyd/errors.go @@ -0,0 +1,7 @@ +package proxyd + +import "fmt" + +func wrapErr(err error, msg string) error { + return fmt.Errorf("%s %v", msg, err) +} diff --git a/proxyd/proxyd/example.config.toml b/proxyd/proxyd/example.config.toml new file mode 100644 index 0000000..2573910 --- /dev/null +++ b/proxyd/proxyd/example.config.toml @@ -0,0 +1,96 @@ +# List of WS methods to whitelist. +ws_method_whitelist = [ + "eth_subscribe", + "eth_call", + "eth_chainId" +] +# Enable WS on this backend group. There can only be one WS-enabled backend group. +ws_backend_group = "main" + +[server] +# Host for the proxyd RPC server to listen on. +rpc_host = "0.0.0.0" +# Port for the above. +rpc_port = 8080 +# Host for the proxyd WS server to listen on. +ws_host = "0.0.0.0" +# Port for the above +ws_port = 8085 +# Maximum client body size, in bytes, that the server will accept. +max_body_size_bytes = 10485760 +max_concurrent_rpcs = 1000 + +[redis] +# URL to a Redis instance. +url = "redis://localhost:6379" + +[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 + +[backend] +# How long proxyd should wait for a backend response before timing out. +response_timeout_seconds = 5 +# Maximum response size, in bytes, that proxyd will accept from a backend. +max_response_size_bytes = 5242880 +# Maximum number of times proxyd will try a backend before giving up. +max_retries = 3 +# Number of seconds to wait before trying an unhealthy backend again. +out_of_service_seconds = 600 + +[backends] +# A map of backends by name. +[backends.infura] +# The URL to contact the backend at. Will be read from the environment +# if an environment variable prefixed with $ is provided. +rpc_url = "" +# The WS URL to contact the backend at. Will be read from the environment +# if an environment variable prefixed with $ is provided. +ws_url = "" +username = "" +# An HTTP Basic password to authenticate with the backend. Will be read from +# the environment if an environment variable prefixed with $ is provided. +password = "" +max_rps = 3 +max_ws_conns = 1 +# Path to a custom root CA. +ca_file = "" +# Path to a custom client cert file. +client_cert_file = "" +# Path to a custom client key file. +client_key_file = "" + +[backends.alchemy] +rpc_url = "" +ws_url = "" +username = "" +password = "" +max_rps = 3 +max_ws_conns = 1 + +[backend_groups] +[backend_groups.main] +backends = ["infura"] + +[backend_groups.alchemy] +backends = ["alchemy"] + +# If the authentication group below is in the config, +# proxyd will only accept authenticated requests. +[authentication] +# Mapping of auth key to alias. The alias is used to provide a human- +# readable name for the auth key in monitoring. The auth key will be +# read from the environment if an environment variable prefixed with $ +# is provided. Note that you will need to quote the environment variable +# in order for it to be value TOML, e.g. "$FOO_AUTH_KEY" = "foo_alias". +secret = "test" + +# Mapping of methods to backend groups. +[rpc_method_mappings] +eth_call = "main" +eth_chainId = "main" +eth_blockNumber = "alchemy" diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod new file mode 100644 index 0000000..9ed5978 --- /dev/null +++ b/proxyd/proxyd/go.mod @@ -0,0 +1,22 @@ +module github.com/ethereum-optimism/optimism/proxyd + +go 1.16 + +require ( + github.com/BurntSushi/toml v0.4.1 + github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect + github.com/alicebob/miniredis v2.5.0+incompatible + github.com/ethereum/go-ethereum v1.10.16 + github.com/go-redis/redis/v8 v8.11.4 + github.com/golang/snappy v0.0.4 + github.com/gomodule/redigo v1.8.8 // indirect + github.com/gorilla/mux v1.8.0 + github.com/gorilla/websocket v1.4.2 + github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d + github.com/prometheus/client_golang v1.11.0 + github.com/rs/cors v1.8.0 + github.com/stretchr/testify v1.7.0 + github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum new file mode 100644 index 0000000..128f28c --- /dev/null +++ b/proxyd/proxyd/go.sum @@ -0,0 +1,702 @@ +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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= +github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= +github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= +github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= +github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= +github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +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/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/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/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= +github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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 v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.10.16 h1:3oPrumn0bCW/idjcxMn5YYVCdK7VzJYIvwGZUGLEaoc= +github.com/ethereum/go-ethereum v1.10.16/go.mod h1:Anj6cxczl+AHy63o4X9O8yWNHuN5wMpfb8MAnHkWn7Y= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= +github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +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.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.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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= +github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/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.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= +github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +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.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +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.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.0.2 h1:RfGLP+h3mvisuWEyybxNq5Eft3NWhHLPeUN72kpKZoI= +github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +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/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +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.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +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 v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= +github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= +github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +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-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-20181114220301-adae6a3d119a/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +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-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +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-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +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-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/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-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190206041539-40960b6deb8e/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +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.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +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/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/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.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/proxyd/proxyd/integration_tests/batch_timeout_test.go b/proxyd/proxyd/integration_tests/batch_timeout_test.go new file mode 100644 index 0000000..372f047 --- /dev/null +++ b/proxyd/proxyd/integration_tests/batch_timeout_test.go @@ -0,0 +1,42 @@ +package integration_tests + +import ( + "net/http" + "os" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/stretchr/testify/require" +) + +const ( + batchTimeoutResponse = `{"error":{"code":-32015,"message":"gateway timeout"},"id":null,"jsonrpc":"2.0"}` +) + +func TestBatchTimeout(t *testing.T) { + slowBackend := NewMockBackend(nil) + defer slowBackend.Close() + + require.NoError(t, os.Setenv("SLOW_BACKEND_RPC_URL", slowBackend.URL())) + + config := ReadConfig("batch_timeout") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + slowBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // check the config. The sleep duration should be at least double the server.timeout_seconds config to prevent flakes + time.Sleep(time.Second * 2) + BatchedResponseHandler(200, goodResponse)(w, r) + })) + res, statusCode, err := client.SendBatchRPC( + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("1", "eth_chainId", nil), + ) + require.NoError(t, err) + require.Equal(t, 504, statusCode) + RequireEqualJSON(t, []byte(batchTimeoutResponse), res) + require.Equal(t, 1, len(slowBackend.Requests())) +} diff --git a/proxyd/proxyd/integration_tests/batching_test.go b/proxyd/proxyd/integration_tests/batching_test.go new file mode 100644 index 0000000..59c6eb5 --- /dev/null +++ b/proxyd/proxyd/integration_tests/batching_test.go @@ -0,0 +1,141 @@ +package integration_tests + +import ( + "net/http" + "os" + "testing" + + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/stretchr/testify/require" +) + +func TestBatching(t *testing.T) { + config := ReadConfig("batching") + + chainIDResponse1 := `{"jsonrpc": "2.0", "result": "hello1", "id": 1}` + chainIDResponse2 := `{"jsonrpc": "2.0", "result": "hello2", "id": 2}` + chainIDResponse3 := `{"jsonrpc": "2.0", "result": "hello3", "id": 3}` + netVersionResponse1 := `{"jsonrpc": "2.0", "result": "1.0", "id": 1}` + callResponse1 := `{"jsonrpc": "2.0", "result": "ekans1", "id": 1}` + + type mockResult struct { + method string + id string + result interface{} + } + + chainIDMock1 := mockResult{"eth_chainId", "1", "hello1"} + chainIDMock2 := mockResult{"eth_chainId", "2", "hello2"} + chainIDMock3 := mockResult{"eth_chainId", "3", "hello3"} + netVersionMock1 := mockResult{"net_version", "1", "1.0"} + callMock1 := mockResult{"eth_call", "1", "ekans1"} + + tests := []struct { + name string + handler http.Handler + mocks []mockResult + reqs []*proxyd.RPCReq + expectedRes string + maxBatchSize int + numExpectedForwards int + }{ + { + name: "backend returns batches out of order", + mocks: []mockResult{chainIDMock1, chainIDMock2, chainIDMock3}, + reqs: []*proxyd.RPCReq{ + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("2", "eth_chainId", nil), + NewRPCReq("3", "eth_chainId", nil), + }, + expectedRes: asArray(chainIDResponse1, chainIDResponse2, chainIDResponse3), + maxBatchSize: 2, + numExpectedForwards: 2, + }, + { + // infura behavior + name: "backend returns single RPC response object as error", + handler: SingleResponseHandler(500, `{"jsonrpc":"2.0","error":{"code":-32001,"message":"internal server error"},"id":1}`), + reqs: []*proxyd.RPCReq{ + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("2", "eth_chainId", nil), + }, + expectedRes: asArray( + `{"error":{"code":-32011,"message":"no backends available for method"},"id":1,"jsonrpc":"2.0"}`, + `{"error":{"code":-32011,"message":"no backends available for method"},"id":2,"jsonrpc":"2.0"}`, + ), + maxBatchSize: 10, + numExpectedForwards: 1, + }, + { + name: "backend returns single RPC response object for minibatches", + handler: SingleResponseHandler(500, `{"jsonrpc":"2.0","error":{"code":-32001,"message":"internal server error"},"id":1}`), + reqs: []*proxyd.RPCReq{ + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("2", "eth_chainId", nil), + }, + expectedRes: asArray( + `{"error":{"code":-32011,"message":"no backends available for method"},"id":1,"jsonrpc":"2.0"}`, + `{"error":{"code":-32011,"message":"no backends available for method"},"id":2,"jsonrpc":"2.0"}`, + ), + maxBatchSize: 1, + numExpectedForwards: 2, + }, + { + name: "duplicate request ids are on distinct batches", + mocks: []mockResult{ + netVersionMock1, + chainIDMock2, + chainIDMock1, + callMock1, + }, + reqs: []*proxyd.RPCReq{ + NewRPCReq("1", "net_version", nil), + NewRPCReq("2", "eth_chainId", nil), + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("1", "eth_call", nil), + }, + expectedRes: asArray(netVersionResponse1, chainIDResponse2, chainIDResponse1, callResponse1), + maxBatchSize: 2, + numExpectedForwards: 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config.Server.MaxUpstreamBatchSize = tt.maxBatchSize + + handler := tt.handler + if handler == nil { + router := NewBatchRPCResponseRouter() + for _, mock := range tt.mocks { + router.SetRoute(mock.method, mock.id, mock.result) + } + handler = router + } + + goodBackend := NewMockBackend(handler) + defer goodBackend.Close() + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + res, statusCode, err := client.SendBatchRPC(tt.reqs...) + require.NoError(t, err) + require.Equal(t, http.StatusOK, statusCode) + RequireEqualJSON(t, []byte(tt.expectedRes), res) + + if tt.numExpectedForwards != 0 { + require.Equal(t, tt.numExpectedForwards, len(goodBackend.Requests())) + } + + if handler, ok := handler.(*BatchRPCResponseRouter); ok { + for i, mock := range tt.mocks { + require.Equal(t, 1, handler.GetNumCalls(mock.method, mock.id), i) + } + } + }) + } +} diff --git a/proxyd/proxyd/integration_tests/caching_test.go b/proxyd/proxyd/integration_tests/caching_test.go new file mode 100644 index 0000000..a75a591 --- /dev/null +++ b/proxyd/proxyd/integration_tests/caching_test.go @@ -0,0 +1,215 @@ +package integration_tests + +import ( + "bytes" + "fmt" + "os" + "testing" + "time" + + "github.com/alicebob/miniredis" + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/stretchr/testify/require" +) + +func TestCaching(t *testing.T) { + redis, err := miniredis.Run() + require.NoError(t, err) + defer redis.Close() + + hdlr := NewBatchRPCResponseRouter() + hdlr.SetRoute("eth_chainId", "999", "0x420") + hdlr.SetRoute("net_version", "999", "0x1234") + hdlr.SetRoute("eth_blockNumber", "999", "0x64") + hdlr.SetRoute("eth_getBlockByNumber", "999", "dummy_block") + hdlr.SetRoute("eth_call", "999", "dummy_call") + + // mock LVC requests + hdlr.SetFallbackRoute("eth_blockNumber", "0x64") + hdlr.SetFallbackRoute("eth_gasPrice", "0x420") + + backend := NewMockBackend(hdlr) + defer backend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) + require.NoError(t, os.Setenv("REDIS_URL", fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()))) + config := ReadConfig("caching") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + // allow time for the block number fetcher to fire + time.Sleep(1500 * time.Millisecond) + + tests := []struct { + method string + params []interface{} + response string + backendCalls int + }{ + { + "eth_chainId", + nil, + "{\"jsonrpc\": \"2.0\", \"result\": \"0x420\", \"id\": 999}", + 1, + }, + { + "net_version", + nil, + "{\"jsonrpc\": \"2.0\", \"result\": \"0x1234\", \"id\": 999}", + 1, + }, + { + "eth_getBlockByNumber", + []interface{}{ + "0x1", + true, + }, + "{\"jsonrpc\": \"2.0\", \"result\": \"dummy_block\", \"id\": 999}", + 1, + }, + { + "eth_call", + []interface{}{ + struct { + To string `json:"to"` + }{ + "0x1234", + }, + "0x60", + }, + "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}", + 1, + }, + { + "eth_blockNumber", + nil, + "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"0x64\"}", + 0, + }, + { + "eth_call", + []interface{}{ + struct { + To string `json:"to"` + }{ + "0x1234", + }, + "latest", + }, + "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}", + 2, + }, + { + "eth_call", + []interface{}{ + struct { + To string `json:"to"` + }{ + "0x1234", + }, + "pending", + }, + "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}", + 2, + }, + } + for _, tt := range tests { + t.Run(tt.method, func(t *testing.T) { + resRaw, _, err := client.SendRPC(tt.method, tt.params) + require.NoError(t, err) + resCache, _, err := client.SendRPC(tt.method, tt.params) + require.NoError(t, err) + RequireEqualJSON(t, []byte(tt.response), resCache) + RequireEqualJSON(t, resRaw, resCache) + require.Equal(t, tt.backendCalls, countRequests(backend, tt.method)) + backend.Reset() + }) + } + + t.Run("block numbers update", func(t *testing.T) { + hdlr.SetFallbackRoute("eth_blockNumber", "0x100") + time.Sleep(1500 * time.Millisecond) + resRaw, _, err := client.SendRPC("eth_blockNumber", nil) + require.NoError(t, err) + RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"0x100\"}"), resRaw) + backend.Reset() + }) + + t.Run("nil responses should not be cached", func(t *testing.T) { + hdlr.SetRoute("eth_getBlockByNumber", "999", nil) + resRaw, _, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x123"}) + require.NoError(t, err) + resCache, _, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x123"}) + require.NoError(t, err) + RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":null}"), resRaw) + RequireEqualJSON(t, resRaw, resCache) + require.Equal(t, 2, countRequests(backend, "eth_getBlockByNumber")) + }) +} + +func TestBatchCaching(t *testing.T) { + redis, err := miniredis.Run() + require.NoError(t, err) + defer redis.Close() + + hdlr := NewBatchRPCResponseRouter() + hdlr.SetRoute("eth_chainId", "1", "0x420") + hdlr.SetRoute("net_version", "1", "0x1234") + hdlr.SetRoute("eth_call", "1", "dummy_call") + + // mock LVC requests + hdlr.SetFallbackRoute("eth_blockNumber", "0x64") + hdlr.SetFallbackRoute("eth_gasPrice", "0x420") + + backend := NewMockBackend(hdlr) + defer backend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) + require.NoError(t, os.Setenv("REDIS_URL", fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()))) + + config := ReadConfig("caching") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + // allow time for the block number fetcher to fire + time.Sleep(1500 * time.Millisecond) + + goodChainIdResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"0x420\", \"id\": 1}" + goodNetVersionResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"0x1234\", \"id\": 1}" + goodEthCallResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"dummy_call\", \"id\": 1}" + + res, _, err := client.SendBatchRPC( + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("1", "net_version", nil), + ) + require.NoError(t, err) + RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodNetVersionResponse)), res) + require.Equal(t, 1, countRequests(backend, "eth_chainId")) + require.Equal(t, 1, countRequests(backend, "net_version")) + + backend.Reset() + res, _, err = client.SendBatchRPC( + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("1", "eth_call", []interface{}{`{"to":"0x1234"}`, "pending"}), + NewRPCReq("1", "net_version", nil), + ) + require.NoError(t, err) + RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodEthCallResponse, goodNetVersionResponse)), res) + require.Equal(t, 0, countRequests(backend, "eth_chainId")) + require.Equal(t, 0, countRequests(backend, "net_version")) + require.Equal(t, 1, countRequests(backend, "eth_call")) +} + +func countRequests(backend *MockBackend, name string) int { + var count int + for _, req := range backend.Requests() { + if bytes.Contains(req.Body, []byte(name)) { + count++ + } + } + return count +} diff --git a/proxyd/proxyd/integration_tests/failover_test.go b/proxyd/proxyd/integration_tests/failover_test.go new file mode 100644 index 0000000..f99d1e6 --- /dev/null +++ b/proxyd/proxyd/integration_tests/failover_test.go @@ -0,0 +1,242 @@ +package integration_tests + +import ( + "fmt" + "net/http" + "os" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/stretchr/testify/require" +) + +const ( + goodResponse = `{"jsonrpc": "2.0", "result": "hello", "id": 999}` + noBackendsResponse = `{"error":{"code":-32011,"message":"no backends available for method"},"id":999,"jsonrpc":"2.0"}` +) + +func TestFailover(t *testing.T) { + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) + defer goodBackend.Close() + badBackend := NewMockBackend(nil) + defer badBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) + + config := ReadConfig("failover") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + tests := []struct { + name string + handler http.Handler + }{ + { + "backend responds 200 with non-JSON response", + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + _, _ = w.Write([]byte("this data is not JSON!")) + }), + }, + { + "backend responds with no body", + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + }), + }, + } + codes := []int{ + 300, + 301, + 302, + 401, + 403, + 429, + 500, + 503, + } + for _, code := range codes { + tests = append(tests, struct { + name string + handler http.Handler + }{ + fmt.Sprintf("backend %d", code), + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(code) + }), + }) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + badBackend.SetHandler(tt.handler) + res, statusCode, err := client.SendRPC("eth_chainId", nil) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + RequireEqualJSON(t, []byte(goodResponse), res) + require.Equal(t, 1, len(badBackend.Requests())) + require.Equal(t, 1, len(goodBackend.Requests())) + badBackend.Reset() + goodBackend.Reset() + }) + } + + t.Run("backend times out and falls back to another", func(t *testing.T) { + badBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(2 * time.Second) + _, _ = w.Write([]byte("[{}]")) + })) + res, statusCode, err := client.SendRPC("eth_chainId", nil) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + RequireEqualJSON(t, []byte(goodResponse), res) + require.Equal(t, 1, len(badBackend.Requests())) + require.Equal(t, 1, len(goodBackend.Requests())) + goodBackend.Reset() + badBackend.Reset() + }) + + t.Run("works with a batch request", func(t *testing.T) { + goodBackend.SetHandler(BatchedResponseHandler(200, goodResponse, goodResponse)) + badBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + })) + res, statusCode, err := client.SendBatchRPC( + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("2", "eth_chainId", nil), + ) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + RequireEqualJSON(t, []byte(asArray(goodResponse, goodResponse)), res) + require.Equal(t, 1, len(badBackend.Requests())) + require.Equal(t, 1, len(goodBackend.Requests())) + goodBackend.Reset() + badBackend.Reset() + }) +} + +func TestRetries(t *testing.T) { + backend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) + defer backend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) + config := ReadConfig("retries") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + attempts := int32(0) + backend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + incremented := atomic.AddInt32(&attempts, 1) + if incremented != 2 { + w.WriteHeader(500) + return + } + BatchedResponseHandler(200, goodResponse)(w, r) + })) + + // test case where request eventually succeeds + res, statusCode, err := client.SendRPC("eth_chainId", nil) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + RequireEqualJSON(t, []byte(goodResponse), res) + require.Equal(t, 2, len(backend.Requests())) + + // test case where it does not + backend.Reset() + attempts = -10 + res, statusCode, err = client.SendRPC("eth_chainId", nil) + require.NoError(t, err) + require.Equal(t, 503, statusCode) + RequireEqualJSON(t, []byte(noBackendsResponse), res) + require.Equal(t, 4, len(backend.Requests())) +} + +func TestOutOfServiceInterval(t *testing.T) { + okHandler := BatchedResponseHandler(200, goodResponse) + goodBackend := NewMockBackend(okHandler) + defer goodBackend.Close() + badBackend := NewMockBackend(nil) + defer badBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) + + config := ReadConfig("out_of_service_interval") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + badBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(503) + })) + + res, statusCode, err := client.SendRPC("eth_chainId", nil) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + RequireEqualJSON(t, []byte(goodResponse), res) + require.Equal(t, 2, len(badBackend.Requests())) + require.Equal(t, 1, len(goodBackend.Requests())) + + res, statusCode, err = client.SendRPC("eth_chainId", nil) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + RequireEqualJSON(t, []byte(goodResponse), res) + require.Equal(t, 2, len(badBackend.Requests())) + require.Equal(t, 2, len(goodBackend.Requests())) + + _, statusCode, err = client.SendBatchRPC( + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("1", "eth_chainId", nil), + ) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + require.Equal(t, 2, len(badBackend.Requests())) + require.Equal(t, 4, len(goodBackend.Requests())) + + time.Sleep(time.Second) + badBackend.SetHandler(okHandler) + + res, statusCode, err = client.SendRPC("eth_chainId", nil) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + RequireEqualJSON(t, []byte(goodResponse), res) + require.Equal(t, 3, len(badBackend.Requests())) + require.Equal(t, 4, len(goodBackend.Requests())) +} + +func TestBatchWithPartialFailover(t *testing.T) { + config := ReadConfig("failover") + config.Server.MaxUpstreamBatchSize = 2 + + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse, goodResponse)) + defer goodBackend.Close() + badBackend := NewMockBackend(SingleResponseHandler(200, "this data is not JSON!")) + defer badBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) + + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + res, statusCode, err := client.SendBatchRPC( + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("2", "eth_chainId", nil), + NewRPCReq("3", "eth_chainId", nil), + NewRPCReq("4", "eth_chainId", nil), + ) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + RequireEqualJSON(t, []byte(asArray(goodResponse, goodResponse, goodResponse, goodResponse)), res) + require.Equal(t, 2, len(badBackend.Requests())) + require.Equal(t, 2, len(goodBackend.Requests())) +} diff --git a/proxyd/proxyd/integration_tests/max_rpc_conns_test.go b/proxyd/proxyd/integration_tests/max_rpc_conns_test.go new file mode 100644 index 0000000..1ee1feb --- /dev/null +++ b/proxyd/proxyd/integration_tests/max_rpc_conns_test.go @@ -0,0 +1,79 @@ +package integration_tests + +import ( + "net/http" + "net/http/httptest" + "os" + "sync" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/stretchr/testify/require" +) + +func TestMaxConcurrentRPCs(t *testing.T) { + var ( + mu sync.Mutex + concurrentRPCs int + maxConcurrentRPCs int + ) + handler := func(w http.ResponseWriter, r *http.Request) { + mu.Lock() + concurrentRPCs++ + if maxConcurrentRPCs < concurrentRPCs { + maxConcurrentRPCs = concurrentRPCs + } + mu.Unlock() + + time.Sleep(time.Second * 2) + BatchedResponseHandler(200, goodResponse)(w, r) + + mu.Lock() + concurrentRPCs-- + mu.Unlock() + } + // We don't use the MockBackend because it serializes requests to the handler + slowBackend := httptest.NewServer(http.HandlerFunc(handler)) + defer slowBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", slowBackend.URL)) + + config := ReadConfig("max_rpc_conns") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + type resWithCodeErr struct { + res []byte + code int + err error + } + resCh := make(chan *resWithCodeErr) + for i := 0; i < 3; i++ { + go func() { + res, code, err := client.SendRPC("eth_chainId", nil) + resCh <- &resWithCodeErr{ + res: res, + code: code, + err: err, + } + }() + } + res1 := <-resCh + res2 := <-resCh + res3 := <-resCh + + require.NoError(t, res1.err) + require.NoError(t, res2.err) + require.NoError(t, res3.err) + require.Equal(t, 200, res1.code) + require.Equal(t, 200, res2.code) + require.Equal(t, 200, res3.code) + RequireEqualJSON(t, []byte(goodResponse), res1.res) + RequireEqualJSON(t, []byte(goodResponse), res2.res) + RequireEqualJSON(t, []byte(goodResponse), res3.res) + + require.EqualValues(t, 2, maxConcurrentRPCs) +} diff --git a/proxyd/proxyd/integration_tests/mock_backend_test.go b/proxyd/proxyd/integration_tests/mock_backend_test.go new file mode 100644 index 0000000..94b00e1 --- /dev/null +++ b/proxyd/proxyd/integration_tests/mock_backend_test.go @@ -0,0 +1,253 @@ +package integration_tests + +import ( + "bytes" + "context" + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "sync" + + "github.com/ethereum-optimism/optimism/proxyd" +) + +type RecordedRequest struct { + Method string + Headers http.Header + Body []byte +} + +type MockBackend struct { + handler http.Handler + server *httptest.Server + mtx sync.RWMutex + requests []*RecordedRequest +} + +func SingleResponseHandler(code int, response string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(code) + _, _ = w.Write([]byte(response)) + } +} + +func BatchedResponseHandler(code int, responses ...string) http.HandlerFunc { + // all proxyd upstream requests are batched + return func(w http.ResponseWriter, r *http.Request) { + var body string + body += "[" + for i, response := range responses { + body += response + if i+1 < len(responses) { + body += "," + } + } + body += "]" + SingleResponseHandler(code, body)(w, r) + } +} + +type responseMapping struct { + result interface{} + calls int +} +type BatchRPCResponseRouter struct { + m map[string]map[string]*responseMapping + fallback map[string]interface{} + mtx sync.Mutex +} + +func NewBatchRPCResponseRouter() *BatchRPCResponseRouter { + return &BatchRPCResponseRouter{ + m: make(map[string]map[string]*responseMapping), + fallback: make(map[string]interface{}), + } +} + +func (h *BatchRPCResponseRouter) SetRoute(method string, id string, result interface{}) { + h.mtx.Lock() + defer h.mtx.Unlock() + + switch result.(type) { + case string: + case nil: + break + default: + panic("invalid result type") + } + + m := h.m[method] + if m == nil { + m = make(map[string]*responseMapping) + } + m[id] = &responseMapping{result: result} + h.m[method] = m +} + +func (h *BatchRPCResponseRouter) SetFallbackRoute(method string, result interface{}) { + h.mtx.Lock() + defer h.mtx.Unlock() + + switch result.(type) { + case string: + case nil: + break + default: + panic("invalid result type") + } + + h.fallback[method] = result +} + +func (h *BatchRPCResponseRouter) GetNumCalls(method string, id string) int { + h.mtx.Lock() + defer h.mtx.Unlock() + + if m := h.m[method]; m != nil { + if rm := m[id]; rm != nil { + return rm.calls + } + } + return 0 +} + +func (h *BatchRPCResponseRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.mtx.Lock() + defer h.mtx.Unlock() + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + + if proxyd.IsBatch(body) { + batch, err := proxyd.ParseBatchRPCReq(body) + if err != nil { + panic(err) + } + out := make([]*proxyd.RPCRes, len(batch)) + for i := range batch { + req, err := proxyd.ParseRPCReq(batch[i]) + if err != nil { + panic(err) + } + + var result interface{} + var resultHasValue bool + + if mappings, exists := h.m[req.Method]; exists { + if rm := mappings[string(req.ID)]; rm != nil { + result = rm.result + resultHasValue = true + rm.calls++ + } + } + if !resultHasValue { + result, resultHasValue = h.fallback[req.Method] + } + if !resultHasValue { + w.WriteHeader(400) + return + } + + out[i] = &proxyd.RPCRes{ + JSONRPC: proxyd.JSONRPCVersion, + Result: result, + ID: req.ID, + } + } + if err := json.NewEncoder(w).Encode(out); err != nil { + panic(err) + } + return + } + + req, err := proxyd.ParseRPCReq(body) + if err != nil { + panic(err) + } + + var result interface{} + var resultHasValue bool + + if mappings, exists := h.m[req.Method]; exists { + if rm := mappings[string(req.ID)]; rm != nil { + result = rm.result + resultHasValue = true + rm.calls++ + } + } + if !resultHasValue { + result, resultHasValue = h.fallback[req.Method] + } + if !resultHasValue { + w.WriteHeader(400) + return + } + + out := &proxyd.RPCRes{ + JSONRPC: proxyd.JSONRPCVersion, + Result: result, + ID: req.ID, + } + enc := json.NewEncoder(w) + if err := enc.Encode(out); err != nil { + panic(err) + } +} + +func NewMockBackend(handler http.Handler) *MockBackend { + mb := &MockBackend{ + handler: handler, + } + mb.server = httptest.NewServer(http.HandlerFunc(mb.wrappedHandler)) + return mb +} + +func (m *MockBackend) URL() string { + return m.server.URL +} + +func (m *MockBackend) Close() { + m.server.Close() +} + +func (m *MockBackend) SetHandler(handler http.Handler) { + m.mtx.Lock() + m.handler = handler + m.mtx.Unlock() +} + +func (m *MockBackend) Reset() { + m.mtx.Lock() + m.requests = nil + m.mtx.Unlock() +} + +func (m *MockBackend) Requests() []*RecordedRequest { + m.mtx.RLock() + defer m.mtx.RUnlock() + out := make([]*RecordedRequest, len(m.requests)) + for i := 0; i < len(m.requests); i++ { + out[i] = m.requests[i] + } + return out +} + +func (m *MockBackend) wrappedHandler(w http.ResponseWriter, r *http.Request) { + m.mtx.Lock() + body, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + clone := r.Clone(context.Background()) + clone.Body = ioutil.NopCloser(bytes.NewReader(body)) + m.requests = append(m.requests, &RecordedRequest{ + Method: r.Method, + Headers: r.Header.Clone(), + Body: body, + }) + m.handler.ServeHTTP(w, clone) + m.mtx.Unlock() +} diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/proxyd/integration_tests/rate_limit_test.go new file mode 100644 index 0000000..409598e --- /dev/null +++ b/proxyd/proxyd/integration_tests/rate_limit_test.go @@ -0,0 +1,60 @@ +package integration_tests + +import ( + "os" + "testing" + + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/stretchr/testify/require" +) + +type resWithCode struct { + code int + res []byte +} + +func TestMaxRPSLimit(t *testing.T) { + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) + defer goodBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + config := ReadConfig("rate_limit") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + resCh := make(chan *resWithCode) + for i := 0; i < 3; i++ { + go func() { + res, code, err := client.SendRPC("eth_chainId", nil) + require.NoError(t, err) + resCh <- &resWithCode{ + code: code, + res: res, + } + }() + } + + codes := make(map[int]int) + var limitedRes []byte + for i := 0; i < 3; i++ { + res := <-resCh + code := res.code + if codes[code] == 0 { + codes[code] = 1 + } else { + codes[code] += 1 + } + + // 503 because there's only one backend available + if code == 503 { + limitedRes = res.res + } + } + + require.Equal(t, 2, codes[200]) + require.Equal(t, 1, codes[503]) + RequireEqualJSON(t, []byte(noBackendsResponse), limitedRes) +} diff --git a/proxyd/proxyd/integration_tests/testdata/batch_timeout.toml b/proxyd/proxyd/integration_tests/testdata/batch_timeout.toml new file mode 100644 index 0000000..4238aaf --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/batch_timeout.toml @@ -0,0 +1,20 @@ +[server] +rpc_port = 8545 +timeout_seconds = 1 +max_upstream_batch_size = 1 + +[backend] +response_timeout_seconds = 1 +max_retries = 3 + +[backends] +[backends.slow] +rpc_url = "$SLOW_BACKEND_RPC_URL" +ws_url = "$SLOW_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["slow"] + +[rpc_method_mappings] +eth_chainId = "main" diff --git a/proxyd/proxyd/integration_tests/testdata/batching.toml b/proxyd/proxyd/integration_tests/testdata/batching.toml new file mode 100644 index 0000000..2aa591d --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/batching.toml @@ -0,0 +1,19 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" +net_version = "main" +eth_call = "main" diff --git a/proxyd/proxyd/integration_tests/testdata/caching.toml b/proxyd/proxyd/integration_tests/testdata/caching.toml new file mode 100644 index 0000000..cd14ff3 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/caching.toml @@ -0,0 +1,29 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 + +[redis] +url = "$REDIS_URL" + +[cache] +enabled = true +block_sync_rpc_url = "$GOOD_BACKEND_RPC_URL" + + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" +net_version = "main" +eth_getBlockByNumber = "main" +eth_blockNumber = "main" +eth_call = "main" diff --git a/proxyd/proxyd/integration_tests/testdata/failover.toml b/proxyd/proxyd/integration_tests/testdata/failover.toml new file mode 100644 index 0000000..80ff990 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/failover.toml @@ -0,0 +1,20 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" +[backends.bad] +rpc_url = "$BAD_BACKEND_RPC_URL" +ws_url = "$BAD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["bad", "good"] + +[rpc_method_mappings] +eth_chainId = "main" \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/testdata/max_rpc_conns.toml b/proxyd/proxyd/integration_tests/testdata/max_rpc_conns.toml new file mode 100644 index 0000000..68d7c19 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/max_rpc_conns.toml @@ -0,0 +1,19 @@ +[server] +rpc_port = 8545 +max_concurrent_rpcs = 2 + +[backend] +# this should cover blocked requests due to max_concurrent_rpcs +response_timeout_seconds = 12 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" diff --git a/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml b/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml new file mode 100644 index 0000000..6663721 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml @@ -0,0 +1,22 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 +max_retries = 1 +out_of_service_seconds = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" +[backends.bad] +rpc_url = "$BAD_BACKEND_RPC_URL" +ws_url = "$BAD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["bad", "good"] + +[rpc_method_mappings] +eth_chainId = "main" \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/testdata/rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/rate_limit.toml new file mode 100644 index 0000000..eca6580 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/rate_limit.toml @@ -0,0 +1,18 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" +max_rps = 2 + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/testdata/retries.toml b/proxyd/proxyd/integration_tests/testdata/retries.toml new file mode 100644 index 0000000..dc9466d --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/retries.toml @@ -0,0 +1,18 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 +max_retries = 3 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/testdata/whitelist.toml b/proxyd/proxyd/integration_tests/testdata/whitelist.toml new file mode 100644 index 0000000..55b118c --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/whitelist.toml @@ -0,0 +1,17 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/util_test.go b/proxyd/proxyd/integration_tests/util_test.go new file mode 100644 index 0000000..bd798ec --- /dev/null +++ b/proxyd/proxyd/integration_tests/util_test.go @@ -0,0 +1,109 @@ +package integration_tests + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "testing" + + "github.com/BurntSushi/toml" + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +type ProxydClient struct { + url string +} + +func NewProxydClient(url string) *ProxydClient { + return &ProxydClient{url: url} +} + +func (p *ProxydClient) SendRPC(method string, params []interface{}) ([]byte, int, error) { + rpcReq := NewRPCReq("999", method, params) + body, err := json.Marshal(rpcReq) + if err != nil { + panic(err) + } + return p.SendRequest(body) +} + +func (p *ProxydClient) SendBatchRPC(reqs ...*proxyd.RPCReq) ([]byte, int, error) { + body, err := json.Marshal(reqs) + if err != nil { + panic(err) + } + return p.SendRequest(body) +} + +func (p *ProxydClient) SendRequest(body []byte) ([]byte, int, error) { + res, err := http.Post(p.url, "application/json", bytes.NewReader(body)) + if err != nil { + return nil, -1, err + } + defer res.Body.Close() + code := res.StatusCode + resBody, err := ioutil.ReadAll(res.Body) + if err != nil { + panic(err) + } + return resBody, code, nil +} + +func RequireEqualJSON(t *testing.T, expected []byte, actual []byte) { + expJSON := canonicalizeJSON(t, expected) + actJSON := canonicalizeJSON(t, actual) + require.Equal(t, string(expJSON), string(actJSON)) +} + +func canonicalizeJSON(t *testing.T, in []byte) []byte { + var any interface{} + if in[0] == '[' { + any = make([]interface{}, 0) + } else { + any = make(map[string]interface{}) + } + + err := json.Unmarshal(in, &any) + require.NoError(t, err) + out, err := json.Marshal(any) + require.NoError(t, err) + return out +} + +func ReadConfig(name string) *proxyd.Config { + config := new(proxyd.Config) + _, err := toml.DecodeFile(fmt.Sprintf("testdata/%s.toml", name), config) + if err != nil { + panic(err) + } + return config +} + +func NewRPCReq(id string, method string, params []interface{}) *proxyd.RPCReq { + jsonParams, err := json.Marshal(params) + if err != nil { + panic(err) + } + + return &proxyd.RPCReq{ + JSONRPC: proxyd.JSONRPCVersion, + Method: method, + Params: jsonParams, + ID: []byte(id), + } +} + +func InitLogger() { + log.Root().SetHandler( + log.LvlFilterHandler(log.LvlDebug, + log.StreamHandler( + os.Stdout, + log.TerminalFormat(false), + )), + ) +} diff --git a/proxyd/proxyd/integration_tests/validation_test.go b/proxyd/proxyd/integration_tests/validation_test.go new file mode 100644 index 0000000..be964c1 --- /dev/null +++ b/proxyd/proxyd/integration_tests/validation_test.go @@ -0,0 +1,232 @@ +package integration_tests + +import ( + "os" + "strings" + "testing" + + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/stretchr/testify/require" +) + +const ( + notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":999}` + parseErrResponse = `{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}` + invalidJSONRPCVersionResponse = `{"error":{"code":-32601,"message":"invalid JSON-RPC version"},"id":null,"jsonrpc":"2.0"}` + invalidIDResponse = `{"error":{"code":-32601,"message":"invalid ID"},"id":null,"jsonrpc":"2.0"}` + invalidMethodResponse = `{"error":{"code":-32601,"message":"no method specified"},"id":null,"jsonrpc":"2.0"}` + invalidBatchLenResponse = `{"error":{"code":-32601,"message":"must specify at least one batch call"},"id":null,"jsonrpc":"2.0"}` +) + +func TestSingleRPCValidation(t *testing.T) { + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) + defer goodBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + config := ReadConfig("whitelist") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + tests := []struct { + name string + body string + res string + code int + }{ + { + "body not JSON", + "this ain't an RPC call", + parseErrResponse, + 400, + }, + { + "body not RPC", + "{\"not\": \"rpc\"}", + invalidJSONRPCVersionResponse, + 400, + }, + { + "body missing RPC ID", + "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23]}", + invalidIDResponse, + 400, + }, + { + "body has array ID", + "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": []}", + invalidIDResponse, + 400, + }, + { + "body has object ID", + "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": {}}", + invalidIDResponse, + 400, + }, + { + "bad method", + "{\"jsonrpc\": \"2.0\", \"method\": 7, \"params\": [42, 23], \"id\": 1}", + parseErrResponse, + 400, + }, + { + "bad JSON-RPC", + "{\"jsonrpc\": \"1.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 1}", + invalidJSONRPCVersionResponse, + 400, + }, + { + "omitted method", + "{\"jsonrpc\": \"2.0\", \"params\": [42, 23], \"id\": 1}", + invalidMethodResponse, + 400, + }, + { + "not whitelisted method", + "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 999}", + notWhitelistedResponse, + 403, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, code, err := client.SendRequest([]byte(tt.body)) + require.NoError(t, err) + RequireEqualJSON(t, []byte(tt.res), res) + require.Equal(t, tt.code, code) + require.Equal(t, 0, len(goodBackend.Requests())) + }) + } +} + +func TestBatchRPCValidation(t *testing.T) { + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) + defer goodBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + config := ReadConfig("whitelist") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + tests := []struct { + name string + body string + res string + code int + reqCount int + }{ + { + "empty batch", + "[]", + invalidBatchLenResponse, + 400, + 0, + }, + { + "bad json", + "[{,]", + parseErrResponse, + 400, + 0, + }, + { + "not object in batch", + "[123]", + asArray(parseErrResponse), + 200, + 0, + }, + { + "body not RPC", + "[{\"not\": \"rpc\"}]", + asArray(invalidJSONRPCVersionResponse), + 200, + 0, + }, + { + "body missing RPC ID", + "[{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23]}]", + asArray(invalidIDResponse), + 200, + 0, + }, + { + "body has array ID", + "[{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": []}]", + asArray(invalidIDResponse), + 200, + 0, + }, + { + "body has object ID", + "[{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": {}}]", + asArray(invalidIDResponse), + 200, + 0, + }, + // this happens because we can't deserialize the method into a non + // string value, and it blows up the parsing for the whole request. + { + "bad method", + "[{\"error\":{\"code\":-32600,\"message\":\"invalid request\"},\"id\":null,\"jsonrpc\":\"2.0\"}]", + asArray(invalidMethodResponse), + 200, + 0, + }, + { + "bad JSON-RPC", + "[{\"jsonrpc\": \"1.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 1}]", + asArray(invalidJSONRPCVersionResponse), + 200, + 0, + }, + { + "omitted method", + "[{\"jsonrpc\": \"2.0\", \"params\": [42, 23], \"id\": 1}]", + asArray(invalidMethodResponse), + 200, + 0, + }, + { + "not whitelisted method", + "[{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 999}]", + asArray(notWhitelistedResponse), + 200, + 0, + }, + { + "mixed", + asArray( + "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 999}", + "{\"jsonrpc\": \"2.0\", \"method\": \"eth_chainId\", \"params\": [], \"id\": 123}", + "123", + ), + asArray( + notWhitelistedResponse, + goodResponse, + parseErrResponse, + ), + 200, + 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, code, err := client.SendRequest([]byte(tt.body)) + require.NoError(t, err) + RequireEqualJSON(t, []byte(tt.res), res) + require.Equal(t, tt.code, code) + require.Equal(t, tt.reqCount, len(goodBackend.Requests())) + }) + } +} + +func asArray(in ...string) string { + return "[" + strings.Join(in, ",") + "]" +} diff --git a/proxyd/proxyd/lvc.go b/proxyd/proxyd/lvc.go new file mode 100644 index 0000000..146bbce --- /dev/null +++ b/proxyd/proxyd/lvc.go @@ -0,0 +1,87 @@ +package proxyd + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" +) + +const cacheSyncRate = 1 * time.Second + +type lvcUpdateFn func(context.Context, *ethclient.Client) (string, error) + +type EthLastValueCache struct { + client *ethclient.Client + cache Cache + key string + updater lvcUpdateFn + quit chan struct{} +} + +func newLVC(client *ethclient.Client, cache Cache, cacheKey string, updater lvcUpdateFn) *EthLastValueCache { + return &EthLastValueCache{ + client: client, + cache: cache, + key: cacheKey, + updater: updater, + quit: make(chan struct{}), + } +} + +func (h *EthLastValueCache) Start() { + go func() { + ticker := time.NewTicker(cacheSyncRate) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + lvcPollTimeGauge.WithLabelValues(h.key).SetToCurrentTime() + + value, err := h.getUpdate() + if err != nil { + log.Error("error retrieving latest value", "key", h.key, "error", err) + continue + } + log.Trace("polling latest value", "value", value) + + if err := h.cache.Put(context.Background(), h.key, value); err != nil { + log.Error("error writing last value to cache", "key", h.key, "error", err) + } + + case <-h.quit: + return + } + } + }() +} + +func (h *EthLastValueCache) getUpdate() (string, error) { + const maxRetries = 5 + var err error + + for i := 0; i <= maxRetries; i++ { + var value string + value, err = h.updater(context.Background(), h.client) + if err != nil { + backoff := calcBackoff(i) + log.Warn("http operation failed. retrying...", "error", err, "backoff", backoff) + lvcErrorsTotal.WithLabelValues(h.key).Inc() + time.Sleep(backoff) + continue + } + return value, nil + } + + return "", wrapErr(err, "exceeded retries") +} + +func (h *EthLastValueCache) Stop() { + close(h.quit) +} + +func (h *EthLastValueCache) Read(ctx context.Context) (string, error) { + return h.cache.Get(ctx, h.key) +} diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go new file mode 100644 index 0000000..4b1731f --- /dev/null +++ b/proxyd/proxyd/methods.go @@ -0,0 +1,399 @@ +package proxyd + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var ( + errInvalidRPCParams = errors.New("invalid RPC params") +) + +type RPCMethodHandler interface { + GetRPCMethod(context.Context, *RPCReq) (*RPCRes, error) + PutRPCMethod(context.Context, *RPCReq, *RPCRes) error +} + +type StaticMethodHandler struct { + cache interface{} + m sync.RWMutex +} + +func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { + e.m.RLock() + cache := e.cache + e.m.RUnlock() + + if cache == nil { + return nil, nil + } + return &RPCRes{ + JSONRPC: req.JSONRPC, + Result: cache, + ID: req.ID, + }, nil +} + +func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { + e.m.Lock() + if e.cache == nil { + e.cache = res.Result + } + e.m.Unlock() + return nil +} + +type EthGetBlockByNumberMethodHandler struct { + cache Cache + getLatestBlockNumFn GetLatestBlockNumFn + numBlockConfirmations int +} + +func (e *EthGetBlockByNumberMethodHandler) cacheKey(req *RPCReq) string { + input, includeTx, err := decodeGetBlockByNumberParams(req.Params) + if err != nil { + return "" + } + return fmt.Sprintf("method:eth_getBlockByNumber:%s:%t", input, includeTx) +} + +func (e *EthGetBlockByNumberMethodHandler) cacheable(req *RPCReq) (bool, error) { + blockNum, _, err := decodeGetBlockByNumberParams(req.Params) + if err != nil { + return false, err + } + return !isBlockDependentParam(blockNum), nil +} + +func (e *EthGetBlockByNumberMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { + if ok, err := e.cacheable(req); !ok || err != nil { + return nil, err + } + key := e.cacheKey(req) + return getImmutableRPCResponse(ctx, e.cache, key, req) +} + +func (e *EthGetBlockByNumberMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { + if ok, err := e.cacheable(req); !ok || err != nil { + return err + } + + blockInput, _, err := decodeGetBlockByNumberParams(req.Params) + if err != nil { + return err + } + if isBlockDependentParam(blockInput) { + return nil + } + if blockInput != "earliest" { + curBlock, err := e.getLatestBlockNumFn(ctx) + if err != nil { + return err + } + blockNum, err := decodeBlockInput(blockInput) + if err != nil { + return err + } + if curBlock <= blockNum+uint64(e.numBlockConfirmations) { + return nil + } + } + + key := e.cacheKey(req) + return putImmutableRPCResponse(ctx, e.cache, key, req, res) +} + +type EthGetBlockRangeMethodHandler struct { + cache Cache + getLatestBlockNumFn GetLatestBlockNumFn + numBlockConfirmations int +} + +func (e *EthGetBlockRangeMethodHandler) cacheKey(req *RPCReq) string { + start, end, includeTx, err := decodeGetBlockRangeParams(req.Params) + if err != nil { + return "" + } + return fmt.Sprintf("method:eth_getBlockRange:%s:%s:%t", start, end, includeTx) +} + +func (e *EthGetBlockRangeMethodHandler) cacheable(req *RPCReq) (bool, error) { + start, end, _, err := decodeGetBlockRangeParams(req.Params) + if err != nil { + return false, err + } + return !isBlockDependentParam(start) && !isBlockDependentParam(end), nil +} + +func (e *EthGetBlockRangeMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { + if ok, err := e.cacheable(req); !ok || err != nil { + return nil, err + } + + key := e.cacheKey(req) + return getImmutableRPCResponse(ctx, e.cache, key, req) +} + +func (e *EthGetBlockRangeMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { + if ok, err := e.cacheable(req); !ok || err != nil { + return err + } + + start, end, _, err := decodeGetBlockRangeParams(req.Params) + if err != nil { + return err + } + curBlock, err := e.getLatestBlockNumFn(ctx) + if err != nil { + return err + } + if start != "earliest" { + startNum, err := decodeBlockInput(start) + if err != nil { + return err + } + if curBlock <= startNum+uint64(e.numBlockConfirmations) { + return nil + } + } + if end != "earliest" { + endNum, err := decodeBlockInput(end) + if err != nil { + return err + } + if curBlock <= endNum+uint64(e.numBlockConfirmations) { + return nil + } + } + + key := e.cacheKey(req) + return putImmutableRPCResponse(ctx, e.cache, key, req, res) +} + +type EthCallMethodHandler struct { + cache Cache + getLatestBlockNumFn GetLatestBlockNumFn + numBlockConfirmations int +} + +func (e *EthCallMethodHandler) cacheable(params *ethCallParams, blockTag string) bool { + if isBlockDependentParam(blockTag) { + return false + } + if params.From != "" || params.Gas != "" { + return false + } + if params.Value != "" && params.Value != "0x0" { + return false + } + return true +} + +func (e *EthCallMethodHandler) cacheKey(params *ethCallParams, blockTag string) string { + keyParams := fmt.Sprintf("%s:%s:%s", params.To, params.Data, blockTag) + return fmt.Sprintf("method:eth_call:%s", keyParams) +} + +func (e *EthCallMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { + params, blockTag, err := decodeEthCallParams(req) + if err != nil { + return nil, err + } + if !e.cacheable(params, blockTag) { + return nil, nil + } + key := e.cacheKey(params, blockTag) + return getImmutableRPCResponse(ctx, e.cache, key, req) +} + +func (e *EthCallMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { + params, blockTag, err := decodeEthCallParams(req) + if err != nil { + return err + } + if !e.cacheable(params, blockTag) { + return nil + } + + if blockTag != "earliest" { + curBlock, err := e.getLatestBlockNumFn(ctx) + if err != nil { + return err + } + blockNum, err := decodeBlockInput(blockTag) + if err != nil { + return err + } + if curBlock <= blockNum+uint64(e.numBlockConfirmations) { + return nil + } + } + + key := e.cacheKey(params, blockTag) + return putImmutableRPCResponse(ctx, e.cache, key, req, res) +} + +type EthBlockNumberMethodHandler struct { + getLatestBlockNumFn GetLatestBlockNumFn +} + +func (e *EthBlockNumberMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { + blockNum, err := e.getLatestBlockNumFn(ctx) + if err != nil { + return nil, err + } + return makeRPCRes(req, hexutil.EncodeUint64(blockNum)), nil +} + +func (e *EthBlockNumberMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRes) error { + return nil +} + +type EthGasPriceMethodHandler struct { + getLatestGasPrice GetLatestGasPriceFn +} + +func (e *EthGasPriceMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { + gasPrice, err := e.getLatestGasPrice(ctx) + if err != nil { + return nil, err + } + return makeRPCRes(req, hexutil.EncodeUint64(gasPrice)), nil +} + +func (e *EthGasPriceMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRes) error { + return nil +} + +func isBlockDependentParam(s string) bool { + return s == "latest" || s == "pending" +} + +func decodeGetBlockByNumberParams(params json.RawMessage) (string, bool, error) { + var list []interface{} + if err := json.Unmarshal(params, &list); err != nil { + return "", false, err + } + if len(list) != 2 { + return "", false, errInvalidRPCParams + } + blockNum, ok := list[0].(string) + if !ok { + return "", false, errInvalidRPCParams + } + includeTx, ok := list[1].(bool) + if !ok { + return "", false, errInvalidRPCParams + } + if !validBlockInput(blockNum) { + return "", false, errInvalidRPCParams + } + return blockNum, includeTx, nil +} + +func decodeGetBlockRangeParams(params json.RawMessage) (string, string, bool, error) { + var list []interface{} + if err := json.Unmarshal(params, &list); err != nil { + return "", "", false, err + } + if len(list) != 3 { + return "", "", false, errInvalidRPCParams + } + startBlockNum, ok := list[0].(string) + if !ok { + return "", "", false, errInvalidRPCParams + } + endBlockNum, ok := list[1].(string) + if !ok { + return "", "", false, errInvalidRPCParams + } + includeTx, ok := list[2].(bool) + if !ok { + return "", "", false, errInvalidRPCParams + } + if !validBlockInput(startBlockNum) || !validBlockInput(endBlockNum) { + return "", "", false, errInvalidRPCParams + } + return startBlockNum, endBlockNum, includeTx, nil +} + +func decodeBlockInput(input string) (uint64, error) { + return hexutil.DecodeUint64(input) +} + +type ethCallParams struct { + From string `json:"from"` + To string `json:"to"` + Gas string `json:"gas"` + GasPrice string `json:"gasPrice"` + Value string `json:"value"` + Data string `json:"data"` +} + +func decodeEthCallParams(req *RPCReq) (*ethCallParams, string, error) { + var input []json.RawMessage + if err := json.Unmarshal(req.Params, &input); err != nil { + return nil, "", err + } + if len(input) != 2 { + return nil, "", fmt.Errorf("invalid eth_call parameters") + } + params := new(ethCallParams) + if err := json.Unmarshal(input[0], params); err != nil { + return nil, "", err + } + var blockTag string + if err := json.Unmarshal(input[1], &blockTag); err != nil { + return nil, "", err + } + return params, blockTag, nil +} + +func validBlockInput(input string) bool { + if input == "earliest" || input == "pending" || input == "latest" { + return true + } + _, err := decodeBlockInput(input) + return err == nil +} + +func makeRPCRes(req *RPCReq, result interface{}) *RPCRes { + return &RPCRes{ + JSONRPC: JSONRPCVersion, + ID: req.ID, + Result: result, + } +} + +func getImmutableRPCResponse(ctx context.Context, cache Cache, key string, req *RPCReq) (*RPCRes, error) { + val, err := cache.Get(ctx, key) + if err != nil { + return nil, err + } + if val == "" { + return nil, nil + } + + var result interface{} + if err := json.Unmarshal([]byte(val), &result); err != nil { + return nil, err + } + return &RPCRes{ + JSONRPC: req.JSONRPC, + Result: result, + ID: req.ID, + }, nil +} + +func putImmutableRPCResponse(ctx context.Context, cache Cache, key string, req *RPCReq, res *RPCRes) error { + if key == "" { + return nil + } + val := mustMarshalJSON(res.Result) + return cache.Put(ctx, key, string(val)) +} diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go new file mode 100644 index 0000000..5241dfa --- /dev/null +++ b/proxyd/proxyd/metrics.go @@ -0,0 +1,280 @@ +package proxyd + +import ( + "context" + "strconv" + "strings" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +const ( + MetricsNamespace = "proxyd" + + RPCRequestSourceHTTP = "http" + RPCRequestSourceWS = "ws" + + BackendProxyd = "proxyd" + SourceClient = "client" + SourceBackend = "backend" + MethodUnknown = "unknown" +) + +var PayloadSizeBuckets = []float64{10, 50, 100, 500, 1000, 5000, 10000, 100000, 1000000} +var MillisecondDurationBuckets = []float64{1, 10, 50, 100, 500, 1000, 5000, 10000, 100000} + +var ( + rpcRequestsTotal = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "rpc_requests_total", + Help: "Count of total client RPC requests.", + }) + + rpcForwardsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "rpc_forwards_total", + Help: "Count of total RPC requests forwarded to each backend.", + }, []string{ + "auth", + "backend_name", + "method_name", + "source", + }) + + rpcBackendHTTPResponseCodesTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "rpc_backend_http_response_codes_total", + Help: "Count of total backend responses by HTTP status code.", + }, []string{ + "auth", + "backend_name", + "method_name", + "status_code", + "batched", + }) + + rpcErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "rpc_errors_total", + Help: "Count of total RPC errors.", + }, []string{ + "auth", + "backend_name", + "method_name", + "error_code", + }) + + rpcSpecialErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "rpc_special_errors_total", + Help: "Count of total special RPC errors.", + }, []string{ + "auth", + "backend_name", + "method_name", + "error_type", + }) + + rpcBackendRequestDurationSumm = promauto.NewSummaryVec(prometheus.SummaryOpts{ + Namespace: MetricsNamespace, + Name: "rpc_backend_request_duration_seconds", + Help: "Summary of backend response times broken down by backend and method name.", + Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.95: 0.005, 0.99: 0.001}, + }, []string{ + "backend_name", + "method_name", + "batched", + }) + + activeClientWsConnsGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "active_client_ws_conns", + Help: "Gauge of active client WS connections.", + }, []string{ + "auth", + }) + + activeBackendWsConnsGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "active_backend_ws_conns", + Help: "Gauge of active backend WS connections.", + }, []string{ + "backend_name", + }) + + unserviceableRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "unserviceable_requests_total", + Help: "Count of total requests that were rejected due to no backends being available.", + }, []string{ + "auth", + "request_source", + }) + + httpResponseCodesTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "http_response_codes_total", + Help: "Count of total HTTP response codes.", + }, []string{ + "status_code", + }) + + httpRequestDurationSumm = promauto.NewSummary(prometheus.SummaryOpts{ + Namespace: MetricsNamespace, + Name: "http_request_duration_seconds", + Help: "Summary of HTTP request durations, in seconds.", + Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.95: 0.005, 0.99: 0.001}, + }) + + wsMessagesTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "ws_messages_total", + Help: "Count of total websocket messages including protocol control.", + }, []string{ + "auth", + "backend_name", + "source", + }) + + redisErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "redis_errors_total", + Help: "Count of total Redis errors.", + }, []string{ + "source", + }) + + requestPayloadSizesGauge = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: MetricsNamespace, + Name: "request_payload_sizes", + Help: "Histogram of client request payload sizes.", + Buckets: PayloadSizeBuckets, + }, []string{ + "auth", + }) + + responsePayloadSizesGauge = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: MetricsNamespace, + Name: "response_payload_sizes", + Help: "Histogram of client response payload sizes.", + Buckets: PayloadSizeBuckets, + }, []string{ + "auth", + }) + + cacheHitsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "cache_hits_total", + Help: "Number of cache hits.", + }, []string{ + "method", + }) + + cacheMissesTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "cache_misses_total", + Help: "Number of cache misses.", + }, []string{ + "method", + }) + + lvcErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "lvc_errors_total", + Help: "Count of lvc errors.", + }, []string{ + "key", + }) + + lvcPollTimeGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "lvc_poll_time_gauge", + Help: "Gauge of lvc poll time.", + }, []string{ + "key", + }) + + batchRPCShortCircuitsTotal = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "batch_rpc_short_circuits_total", + Help: "Count of total batch RPC short-circuits.", + }) + + rpcSpecialErrors = []string{ + "nonce too low", + "gas price too high", + "gas price too low", + "invalid parameters", + } + + redisCacheDurationSumm = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: MetricsNamespace, + Name: "redis_cache_duration_milliseconds", + Help: "Histogram of Redis command durations, in milliseconds.", + Buckets: MillisecondDurationBuckets, + }, []string{"command"}) + + tooManyRequestErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "too_many_request_errors_total", + Help: "Count of request timeouts due to too many concurrent RPCs.", + }, []string{ + "backend_name", + }) +) + +func RecordRedisError(source string) { + redisErrorsTotal.WithLabelValues(source).Inc() +} + +func RecordRPCError(ctx context.Context, backendName, method string, err error) { + rpcErr, ok := err.(*RPCErr) + var code int + if ok { + MaybeRecordSpecialRPCError(ctx, backendName, method, rpcErr) + code = rpcErr.Code + } else { + code = -1 + } + + rpcErrorsTotal.WithLabelValues(GetAuthCtx(ctx), backendName, method, strconv.Itoa(code)).Inc() +} + +func RecordWSMessage(ctx context.Context, backendName, source string) { + wsMessagesTotal.WithLabelValues(GetAuthCtx(ctx), backendName, source).Inc() +} + +func RecordUnserviceableRequest(ctx context.Context, source string) { + unserviceableRequestsTotal.WithLabelValues(GetAuthCtx(ctx), source).Inc() +} + +func RecordRPCForward(ctx context.Context, backendName, method, source string) { + rpcForwardsTotal.WithLabelValues(GetAuthCtx(ctx), backendName, method, source).Inc() +} + +func MaybeRecordSpecialRPCError(ctx context.Context, backendName, method string, rpcErr *RPCErr) { + errMsg := strings.ToLower(rpcErr.Message) + for _, errStr := range rpcSpecialErrors { + if strings.Contains(errMsg, errStr) { + rpcSpecialErrorsTotal.WithLabelValues(GetAuthCtx(ctx), backendName, method, errStr).Inc() + return + } + } +} + +func RecordRequestPayloadSize(ctx context.Context, payloadSize int) { + requestPayloadSizesGauge.WithLabelValues(GetAuthCtx(ctx)).Observe(float64(payloadSize)) +} + +func RecordResponsePayloadSize(ctx context.Context, payloadSize int) { + responsePayloadSizesGauge.WithLabelValues(GetAuthCtx(ctx)).Observe(float64(payloadSize)) +} + +func RecordCacheHit(method string) { + cacheHitsTotal.WithLabelValues(method).Inc() +} + +func RecordCacheMiss(method string) { + cacheMissesTotal.WithLabelValues(method).Inc() +} diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json new file mode 100644 index 0000000..dbdbead --- /dev/null +++ b/proxyd/proxyd/package.json @@ -0,0 +1,6 @@ +{ + "name": "@eth-optimism/proxyd", + "version": "3.8.5", + "private": true, + "dependencies": {} +} diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go new file mode 100644 index 0000000..a730a60 --- /dev/null +++ b/proxyd/proxyd/proxyd.go @@ -0,0 +1,344 @@ +package proxyd + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net/http" + "os" + "strconv" + "time" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/prometheus/client_golang/prometheus/promhttp" + "golang.org/x/sync/semaphore" +) + +func Start(config *Config) (func(), error) { + if len(config.Backends) == 0 { + return nil, errors.New("must define at least one backend") + } + if len(config.BackendGroups) == 0 { + return nil, errors.New("must define at least one backend group") + } + if len(config.RPCMethodMappings) == 0 { + return nil, errors.New("must define at least one RPC method mapping") + } + + for authKey := range config.Authentication { + if authKey == "none" { + return nil, errors.New("cannot use none as an auth key") + } + } + + var redisURL string + if config.Redis.URL != "" { + rURL, err := ReadFromEnvOrConfig(config.Redis.URL) + if err != nil { + return nil, err + } + redisURL = rURL + } + + var lim RateLimiter + var err error + if redisURL == "" { + log.Warn("redis is not configured, using local rate limiter") + lim = NewLocalRateLimiter() + } else { + lim, err = NewRedisRateLimiter(redisURL) + if err != nil { + return nil, err + } + } + + maxConcurrentRPCs := config.Server.MaxConcurrentRPCs + if maxConcurrentRPCs == 0 { + maxConcurrentRPCs = math.MaxInt64 + } + rpcRequestSemaphore := semaphore.NewWeighted(maxConcurrentRPCs) + + backendNames := make([]string, 0) + backendsByName := make(map[string]*Backend) + for name, cfg := range config.Backends { + opts := make([]BackendOpt, 0) + + rpcURL, err := ReadFromEnvOrConfig(cfg.RPCURL) + if err != nil { + return nil, err + } + wsURL, err := ReadFromEnvOrConfig(cfg.WSURL) + if err != nil { + return nil, err + } + if rpcURL == "" { + return nil, fmt.Errorf("must define an RPC URL for backend %s", name) + } + if wsURL == "" { + return nil, fmt.Errorf("must define a WS URL for backend %s", name) + } + + if config.BackendOptions.ResponseTimeoutSeconds != 0 { + timeout := secondsToDuration(config.BackendOptions.ResponseTimeoutSeconds) + opts = append(opts, WithTimeout(timeout)) + } + if config.BackendOptions.MaxRetries != 0 { + opts = append(opts, WithMaxRetries(config.BackendOptions.MaxRetries)) + } + if config.BackendOptions.MaxResponseSizeBytes != 0 { + opts = append(opts, WithMaxResponseSize(config.BackendOptions.MaxResponseSizeBytes)) + } + if config.BackendOptions.OutOfServiceSeconds != 0 { + opts = append(opts, WithOutOfServiceDuration(secondsToDuration(config.BackendOptions.OutOfServiceSeconds))) + } + if cfg.MaxRPS != 0 { + opts = append(opts, WithMaxRPS(cfg.MaxRPS)) + } + if cfg.MaxWSConns != 0 { + opts = append(opts, WithMaxWSConns(cfg.MaxWSConns)) + } + if cfg.Password != "" { + passwordVal, err := ReadFromEnvOrConfig(cfg.Password) + if err != nil { + return nil, err + } + opts = append(opts, WithBasicAuth(cfg.Username, passwordVal)) + } + tlsConfig, err := configureBackendTLS(cfg) + if err != nil { + return nil, err + } + if tlsConfig != nil { + log.Info("using custom TLS config for backend", "name", name) + opts = append(opts, WithTLSConfig(tlsConfig)) + } + if cfg.StripTrailingXFF { + opts = append(opts, WithStrippedTrailingXFF()) + } + opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP"))) + back := NewBackend(name, rpcURL, wsURL, lim, rpcRequestSemaphore, opts...) + backendNames = append(backendNames, name) + backendsByName[name] = back + log.Info("configured backend", "name", name, "rpc_url", rpcURL, "ws_url", wsURL) + } + + backendGroups := make(map[string]*BackendGroup) + for bgName, bg := range config.BackendGroups { + backends := make([]*Backend, 0) + for _, bName := range bg.Backends { + if backendsByName[bName] == nil { + return nil, fmt.Errorf("backend %s is not defined", bName) + } + backends = append(backends, backendsByName[bName]) + } + group := &BackendGroup{ + Name: bgName, + Backends: backends, + } + backendGroups[bgName] = group + } + + var wsBackendGroup *BackendGroup + if config.WSBackendGroup != "" { + wsBackendGroup = backendGroups[config.WSBackendGroup] + if wsBackendGroup == nil { + return nil, fmt.Errorf("ws backend group %s does not exist", config.WSBackendGroup) + } + } + + if wsBackendGroup == nil && config.Server.WSPort != 0 { + return nil, fmt.Errorf("a ws port was defined, but no ws group was defined") + } + + for _, bg := range config.RPCMethodMappings { + if backendGroups[bg] == nil { + return nil, fmt.Errorf("undefined backend group %s", bg) + } + } + + var resolvedAuth map[string]string + + if config.Authentication != nil { + resolvedAuth = make(map[string]string) + for secret, alias := range config.Authentication { + resolvedSecret, err := ReadFromEnvOrConfig(secret) + if err != nil { + return nil, err + } + resolvedAuth[resolvedSecret] = alias + } + } + + var ( + rpcCache RPCCache + blockNumLVC *EthLastValueCache + gasPriceLVC *EthLastValueCache + ) + if config.Cache.Enabled { + var ( + cache Cache + blockNumFn GetLatestBlockNumFn + gasPriceFn GetLatestGasPriceFn + ) + + if config.Cache.BlockSyncRPCURL == "" { + return nil, fmt.Errorf("block sync node required for caching") + } + blockSyncRPCURL, err := ReadFromEnvOrConfig(config.Cache.BlockSyncRPCURL) + if err != nil { + return nil, err + } + + if redisURL != "" { + if cache, err = newRedisCache(redisURL); err != nil { + return nil, err + } + } else { + log.Warn("redis is not configured, using in-memory cache") + cache = newMemoryCache() + } + // Ideally, the BlocKSyncRPCURL should be the sequencer or a HA replica that's not far behind + ethClient, err := ethclient.Dial(blockSyncRPCURL) + if err != nil { + return nil, err + } + defer ethClient.Close() + + blockNumLVC, blockNumFn = makeGetLatestBlockNumFn(ethClient, cache) + gasPriceLVC, gasPriceFn = makeGetLatestGasPriceFn(ethClient, cache) + rpcCache = newRPCCache(newCacheWithCompression(cache), blockNumFn, gasPriceFn, config.Cache.NumBlockConfirmations) + } + + srv := NewServer( + backendGroups, + wsBackendGroup, + NewStringSetFromStrings(config.WSMethodWhitelist), + config.RPCMethodMappings, + config.Server.MaxBodySizeBytes, + resolvedAuth, + secondsToDuration(config.Server.TimeoutSeconds), + config.Server.MaxUpstreamBatchSize, + rpcCache, + ) + + if config.Metrics.Enabled { + addr := fmt.Sprintf("%s:%d", config.Metrics.Host, config.Metrics.Port) + log.Info("starting metrics server", "addr", addr) + go func() { + if err := http.ListenAndServe(addr, promhttp.Handler()); err != nil { + log.Error("error starting metrics server", "err", err) + } + }() + } + + // To allow integration tests to cleanly come up, wait + // 10ms to give the below goroutines enough time to + // encounter an error creating their servers + errTimer := time.NewTimer(10 * time.Millisecond) + + if config.Server.RPCPort != 0 { + go func() { + if err := srv.RPCListenAndServe(config.Server.RPCHost, config.Server.RPCPort); err != nil { + if errors.Is(err, http.ErrServerClosed) { + log.Info("RPC server shut down") + return + } + log.Crit("error starting RPC server", "err", err) + } + }() + } + + if config.Server.WSPort != 0 { + go func() { + if err := srv.WSListenAndServe(config.Server.WSHost, config.Server.WSPort); err != nil { + if errors.Is(err, http.ErrServerClosed) { + log.Info("WS server shut down") + return + } + log.Crit("error starting WS server", "err", err) + } + }() + } + + <-errTimer.C + log.Info("started proxyd") + + return func() { + log.Info("shutting down proxyd") + if blockNumLVC != nil { + blockNumLVC.Stop() + } + if gasPriceLVC != nil { + gasPriceLVC.Stop() + } + srv.Shutdown() + if err := lim.FlushBackendWSConns(backendNames); err != nil { + log.Error("error flushing backend ws conns", "err", err) + } + log.Info("goodbye") + }, nil +} + +func secondsToDuration(seconds int) time.Duration { + return time.Duration(seconds) * time.Second +} + +func configureBackendTLS(cfg *BackendConfig) (*tls.Config, error) { + if cfg.CAFile == "" { + return nil, nil + } + + tlsConfig, err := CreateTLSClient(cfg.CAFile) + if err != nil { + return nil, err + } + + if cfg.ClientCertFile != "" && cfg.ClientKeyFile != "" { + cert, err := ParseKeyPair(cfg.ClientCertFile, cfg.ClientKeyFile) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + + return tlsConfig, nil +} + +func makeUint64LastValueFn(client *ethclient.Client, cache Cache, key string, updater lvcUpdateFn) (*EthLastValueCache, func(context.Context) (uint64, error)) { + lvc := newLVC(client, cache, key, updater) + lvc.Start() + return lvc, func(ctx context.Context) (uint64, error) { + value, err := lvc.Read(ctx) + if err != nil { + return 0, err + } + if value == "" { + return 0, fmt.Errorf("%s is unavailable", key) + } + valueUint, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return 0, err + } + return valueUint, nil + } +} + +func makeGetLatestBlockNumFn(client *ethclient.Client, cache Cache) (*EthLastValueCache, GetLatestBlockNumFn) { + return makeUint64LastValueFn(client, cache, "lvc:block_number", func(ctx context.Context, c *ethclient.Client) (string, error) { + blockNum, err := c.BlockNumber(ctx) + return strconv.FormatUint(blockNum, 10), err + }) +} + +func makeGetLatestGasPriceFn(client *ethclient.Client, cache Cache) (*EthLastValueCache, GetLatestGasPriceFn) { + return makeUint64LastValueFn(client, cache, "lvc:gas_price", func(ctx context.Context, c *ethclient.Client) (string, error) { + gasPrice, err := c.SuggestGasPrice(ctx) + if err != nil { + return "", err + } + return gasPrice.String(), nil + }) +} diff --git a/proxyd/proxyd/rate_limiter.go b/proxyd/proxyd/rate_limiter.go new file mode 100644 index 0000000..5c4a4d6 --- /dev/null +++ b/proxyd/proxyd/rate_limiter.go @@ -0,0 +1,265 @@ +package proxyd + +import ( + "context" + "crypto/rand" + "encoding/hex" + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/go-redis/redis/v8" +) + +const MaxRPSScript = ` +local current +current = redis.call("incr", KEYS[1]) +if current == 1 then + redis.call("expire", KEYS[1], 1) +end +return current +` + +const MaxConcurrentWSConnsScript = ` +redis.call("sadd", KEYS[1], KEYS[2]) +local total = 0 +local scanres = redis.call("sscan", KEYS[1], 0) +for _, k in ipairs(scanres[2]) do + local value = redis.call("get", k) + if value then + total = total + value + end +end + +if total < tonumber(ARGV[1]) then + redis.call("incr", KEYS[2]) + redis.call("expire", KEYS[2], 300) + return true +end + +return false +` + +type RateLimiter interface { + IsBackendOnline(name string) (bool, error) + SetBackendOffline(name string, duration time.Duration) error + IncBackendRPS(name string) (int, error) + IncBackendWSConns(name string, max int) (bool, error) + DecBackendWSConns(name string) error + FlushBackendWSConns(names []string) error +} + +type RedisRateLimiter struct { + rdb *redis.Client + randID string + touchKeys map[string]time.Duration + tkMtx sync.Mutex +} + +func NewRedisRateLimiter(url string) (RateLimiter, error) { + opts, err := redis.ParseURL(url) + if err != nil { + return nil, err + } + rdb := redis.NewClient(opts) + if err := rdb.Ping(context.Background()).Err(); err != nil { + return nil, wrapErr(err, "error connecting to redis") + } + out := &RedisRateLimiter{ + rdb: rdb, + randID: randStr(20), + touchKeys: make(map[string]time.Duration), + } + go out.touch() + return out, nil +} + +func (r *RedisRateLimiter) IsBackendOnline(name string) (bool, error) { + exists, err := r.rdb.Exists(context.Background(), fmt.Sprintf("backend:%s:offline", name)).Result() + if err != nil { + RecordRedisError("IsBackendOnline") + return false, wrapErr(err, "error getting backend availability") + } + + return exists == 0, nil +} + +func (r *RedisRateLimiter) SetBackendOffline(name string, duration time.Duration) error { + if duration == 0 { + return nil + } + err := r.rdb.SetEX( + context.Background(), + fmt.Sprintf("backend:%s:offline", name), + 1, + duration, + ).Err() + if err != nil { + RecordRedisError("SetBackendOffline") + return wrapErr(err, "error setting backend unavailable") + } + return nil +} + +func (r *RedisRateLimiter) IncBackendRPS(name string) (int, error) { + cmd := r.rdb.Eval( + context.Background(), + MaxRPSScript, + []string{fmt.Sprintf("backend:%s:ratelimit", name)}, + ) + rps, err := cmd.Int() + if err != nil { + RecordRedisError("IncBackendRPS") + return -1, wrapErr(err, "error upserting backend rate limit") + } + return rps, nil +} + +func (r *RedisRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { + connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) + r.tkMtx.Lock() + r.touchKeys[connsKey] = 5 * time.Minute + r.tkMtx.Unlock() + cmd := r.rdb.Eval( + context.Background(), + MaxConcurrentWSConnsScript, + []string{ + fmt.Sprintf("backend:%s:proxies", name), + connsKey, + }, + max, + ) + incremented, err := cmd.Bool() + // false gets coerced to redis.nil, see https://redis.io/commands/eval#conversion-between-lua-and-redis-data-types + if err == redis.Nil { + return false, nil + } + if err != nil { + RecordRedisError("IncBackendWSConns") + return false, wrapErr(err, "error incrementing backend ws conns") + } + return incremented, nil +} + +func (r *RedisRateLimiter) DecBackendWSConns(name string) error { + connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) + err := r.rdb.Decr(context.Background(), connsKey).Err() + if err != nil { + RecordRedisError("DecBackendWSConns") + return wrapErr(err, "error decrementing backend ws conns") + } + return nil +} + +func (r *RedisRateLimiter) FlushBackendWSConns(names []string) error { + ctx := context.Background() + for _, name := range names { + connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) + err := r.rdb.SRem( + ctx, + fmt.Sprintf("backend:%s:proxies", name), + connsKey, + ).Err() + if err != nil { + return wrapErr(err, "error flushing backend ws conns") + } + err = r.rdb.Del(ctx, connsKey).Err() + if err != nil { + return wrapErr(err, "error flushing backend ws conns") + } + } + return nil +} + +func (r *RedisRateLimiter) touch() { + for { + r.tkMtx.Lock() + for key, dur := range r.touchKeys { + if err := r.rdb.Expire(context.Background(), key, dur).Err(); err != nil { + RecordRedisError("touch") + log.Error("error touching redis key", "key", key, "err", err) + } + } + r.tkMtx.Unlock() + time.Sleep(5 * time.Second) + } +} + +type LocalRateLimiter struct { + deadBackends map[string]time.Time + backendRPS map[string]int + backendWSConns map[string]int + mtx sync.RWMutex +} + +func NewLocalRateLimiter() *LocalRateLimiter { + out := &LocalRateLimiter{ + deadBackends: make(map[string]time.Time), + backendRPS: make(map[string]int), + backendWSConns: make(map[string]int), + } + go out.clear() + return out +} + +func (l *LocalRateLimiter) IsBackendOnline(name string) (bool, error) { + l.mtx.RLock() + defer l.mtx.RUnlock() + return l.deadBackends[name].Before(time.Now()), nil +} + +func (l *LocalRateLimiter) SetBackendOffline(name string, duration time.Duration) error { + l.mtx.Lock() + defer l.mtx.Unlock() + l.deadBackends[name] = time.Now().Add(duration) + return nil +} + +func (l *LocalRateLimiter) IncBackendRPS(name string) (int, error) { + l.mtx.Lock() + defer l.mtx.Unlock() + l.backendRPS[name] += 1 + return l.backendRPS[name], nil +} + +func (l *LocalRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { + l.mtx.Lock() + defer l.mtx.Unlock() + if l.backendWSConns[name] == max { + return false, nil + } + l.backendWSConns[name] += 1 + return true, nil +} + +func (l *LocalRateLimiter) DecBackendWSConns(name string) error { + l.mtx.Lock() + defer l.mtx.Unlock() + if l.backendWSConns[name] == 0 { + return nil + } + l.backendWSConns[name] -= 1 + return nil +} + +func (l *LocalRateLimiter) FlushBackendWSConns(names []string) error { + return nil +} + +func (l *LocalRateLimiter) clear() { + for { + time.Sleep(time.Second) + l.mtx.Lock() + l.backendRPS = make(map[string]int) + l.mtx.Unlock() + } +} + +func randStr(l int) string { + b := make([]byte, l) + if _, err := rand.Read(b); err != nil { + panic(err) + } + return hex.EncodeToString(b) +} diff --git a/proxyd/proxyd/rpc.go b/proxyd/proxyd/rpc.go new file mode 100644 index 0000000..7809132 --- /dev/null +++ b/proxyd/proxyd/rpc.go @@ -0,0 +1,154 @@ +package proxyd + +import ( + "encoding/json" + "io" + "io/ioutil" + "strings" +) + +type RPCReq struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params json.RawMessage `json:"params"` + ID json.RawMessage `json:"id"` +} + +type RPCRes struct { + JSONRPC string + Result interface{} + Error *RPCErr + ID json.RawMessage +} + +type rpcResJSON struct { + JSONRPC string `json:"jsonrpc"` + Result interface{} `json:"result,omitempty"` + Error *RPCErr `json:"error,omitempty"` + ID json.RawMessage `json:"id"` +} + +type nullResultRPCRes struct { + JSONRPC string `json:"jsonrpc"` + Result interface{} `json:"result"` + ID json.RawMessage `json:"id"` +} + +func (r *RPCRes) IsError() bool { + return r.Error != nil +} + +func (r *RPCRes) MarshalJSON() ([]byte, error) { + if r.Result == nil && r.Error == nil { + return json.Marshal(&nullResultRPCRes{ + JSONRPC: r.JSONRPC, + Result: nil, + ID: r.ID, + }) + } + + return json.Marshal(&rpcResJSON{ + JSONRPC: r.JSONRPC, + Result: r.Result, + Error: r.Error, + ID: r.ID, + }) +} + +type RPCErr struct { + Code int `json:"code"` + Message string `json:"message"` + HTTPErrorCode int `json:"-"` +} + +func (r *RPCErr) Error() string { + return r.Message +} + +func IsValidID(id json.RawMessage) bool { + // handle the case where the ID is a string + if strings.HasPrefix(string(id), "\"") && strings.HasSuffix(string(id), "\"") { + return len(id) > 2 + } + + // technically allows a boolean/null ID, but so does Geth + // https://github.com/ethereum/go-ethereum/blob/master/rpc/json.go#L72 + return len(id) > 0 && id[0] != '{' && id[0] != '[' +} + +func ParseRPCReq(body []byte) (*RPCReq, error) { + req := new(RPCReq) + if err := json.Unmarshal(body, req); err != nil { + return nil, ErrParseErr + } + + return req, nil +} + +func ParseBatchRPCReq(body []byte) ([]json.RawMessage, error) { + batch := make([]json.RawMessage, 0) + if err := json.Unmarshal(body, &batch); err != nil { + return nil, err + } + + return batch, nil +} + +func ParseRPCRes(r io.Reader) (*RPCRes, error) { + body, err := ioutil.ReadAll(r) + if err != nil { + return nil, wrapErr(err, "error reading RPC response") + } + + res := new(RPCRes) + if err := json.Unmarshal(body, res); err != nil { + return nil, wrapErr(err, "error unmarshaling RPC response") + } + + return res, nil +} + +func ValidateRPCReq(req *RPCReq) error { + if req.JSONRPC != JSONRPCVersion { + return ErrInvalidRequest("invalid JSON-RPC version") + } + + if req.Method == "" { + return ErrInvalidRequest("no method specified") + } + + if !IsValidID(req.ID) { + return ErrInvalidRequest("invalid ID") + } + + return nil +} + +func NewRPCErrorRes(id json.RawMessage, err error) *RPCRes { + var rpcErr *RPCErr + if rr, ok := err.(*RPCErr); ok { + rpcErr = rr + } else { + rpcErr = &RPCErr{ + Code: JSONRPCErrorInternal, + Message: err.Error(), + } + } + + return &RPCRes{ + JSONRPC: JSONRPCVersion, + Error: rpcErr, + ID: id, + } +} + +func IsBatch(raw []byte) bool { + for _, c := range raw { + // skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt) + if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d { + continue + } + return c == '[' + } + return false +} diff --git a/proxyd/proxyd/rpc_test.go b/proxyd/proxyd/rpc_test.go new file mode 100644 index 0000000..0d38dec --- /dev/null +++ b/proxyd/proxyd/rpc_test.go @@ -0,0 +1,76 @@ +package proxyd + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRPCResJSON(t *testing.T) { + tests := []struct { + name string + in *RPCRes + out string + }{ + { + "string result", + &RPCRes{ + JSONRPC: JSONRPCVersion, + Result: "foobar", + ID: []byte("123"), + }, + `{"jsonrpc":"2.0","result":"foobar","id":123}`, + }, + { + "object result", + &RPCRes{ + JSONRPC: JSONRPCVersion, + Result: struct { + Str string `json:"str"` + }{ + "test", + }, + ID: []byte("123"), + }, + `{"jsonrpc":"2.0","result":{"str":"test"},"id":123}`, + }, + { + "nil result", + &RPCRes{ + JSONRPC: JSONRPCVersion, + Result: nil, + ID: []byte("123"), + }, + `{"jsonrpc":"2.0","result":null,"id":123}`, + }, + { + "error result", + &RPCRes{ + JSONRPC: JSONRPCVersion, + Error: &RPCErr{ + Code: 1234, + Message: "test err", + }, + ID: []byte("123"), + }, + `{"jsonrpc":"2.0","error":{"code":1234,"message":"test err"},"id":123}`, + }, + { + "string ID", + &RPCRes{ + JSONRPC: JSONRPCVersion, + Result: "foobar", + ID: []byte("\"123\""), + }, + `{"jsonrpc":"2.0","result":"foobar","id":"123"}`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + out, err := json.Marshal(tt.in) + require.NoError(t, err) + require.Equal(t, tt.out, string(out)) + }) + } +} diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go new file mode 100644 index 0000000..30559d9 --- /dev/null +++ b/proxyd/proxyd/server.go @@ -0,0 +1,533 @@ +package proxyd + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "net/http" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/gorilla/mux" + "github.com/gorilla/websocket" + "github.com/prometheus/client_golang/prometheus" + "github.com/rs/cors" +) + +const ( + ContextKeyAuth = "authorization" + ContextKeyReqID = "req_id" + ContextKeyXForwardedFor = "x_forwarded_for" + MaxBatchRPCCalls = 100 + cacheStatusHdr = "X-Proxyd-Cache-Status" + defaultServerTimeout = time.Second * 10 + maxLogLength = 2000 + defaultMaxUpstreamBatchSize = 10 +) + +type Server struct { + backendGroups map[string]*BackendGroup + wsBackendGroup *BackendGroup + wsMethodWhitelist *StringSet + rpcMethodMappings map[string]string + maxBodySize int64 + authenticatedPaths map[string]string + timeout time.Duration + maxUpstreamBatchSize int + upgrader *websocket.Upgrader + rpcServer *http.Server + wsServer *http.Server + cache RPCCache +} + +func NewServer( + backendGroups map[string]*BackendGroup, + wsBackendGroup *BackendGroup, + wsMethodWhitelist *StringSet, + rpcMethodMappings map[string]string, + maxBodySize int64, + authenticatedPaths map[string]string, + timeout time.Duration, + maxUpstreamBatchSize int, + cache RPCCache, +) *Server { + if cache == nil { + cache = &NoopRPCCache{} + } + + if maxBodySize == 0 { + maxBodySize = math.MaxInt64 + } + + if timeout == 0 { + timeout = defaultServerTimeout + } + + if maxUpstreamBatchSize == 0 { + maxUpstreamBatchSize = defaultMaxUpstreamBatchSize + } + + return &Server{ + backendGroups: backendGroups, + wsBackendGroup: wsBackendGroup, + wsMethodWhitelist: wsMethodWhitelist, + rpcMethodMappings: rpcMethodMappings, + maxBodySize: maxBodySize, + authenticatedPaths: authenticatedPaths, + timeout: timeout, + maxUpstreamBatchSize: maxUpstreamBatchSize, + cache: cache, + upgrader: &websocket.Upgrader{ + HandshakeTimeout: 5 * time.Second, + }, + } +} + +func (s *Server) RPCListenAndServe(host string, port int) error { + hdlr := mux.NewRouter() + hdlr.HandleFunc("/healthz", s.HandleHealthz).Methods("GET") + hdlr.HandleFunc("/", s.HandleRPC).Methods("POST") + hdlr.HandleFunc("/{authorization}", s.HandleRPC).Methods("POST") + c := cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, + }) + addr := fmt.Sprintf("%s:%d", host, port) + s.rpcServer = &http.Server{ + Handler: instrumentedHdlr(c.Handler(hdlr)), + Addr: addr, + } + log.Info("starting HTTP server", "addr", addr) + return s.rpcServer.ListenAndServe() +} + +func (s *Server) WSListenAndServe(host string, port int) error { + hdlr := mux.NewRouter() + hdlr.HandleFunc("/", s.HandleWS) + hdlr.HandleFunc("/{authorization}", s.HandleWS) + c := cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, + }) + addr := fmt.Sprintf("%s:%d", host, port) + s.wsServer = &http.Server{ + Handler: instrumentedHdlr(c.Handler(hdlr)), + Addr: addr, + } + log.Info("starting WS server", "addr", addr) + return s.wsServer.ListenAndServe() +} + +func (s *Server) Shutdown() { + if s.rpcServer != nil { + _ = s.rpcServer.Shutdown(context.Background()) + } + if s.wsServer != nil { + _ = s.wsServer.Shutdown(context.Background()) + } +} + +func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("OK")) +} + +func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { + ctx := s.populateContext(w, r) + if ctx == nil { + return + } + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, s.timeout) + defer cancel() + + log.Info( + "received RPC request", + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + "user_agent", r.Header.Get("user-agent"), + ) + + body, err := ioutil.ReadAll(io.LimitReader(r.Body, s.maxBodySize)) + if err != nil { + log.Error("error reading request body", "err", err) + writeRPCError(ctx, w, nil, ErrInternal) + return + } + RecordRequestPayloadSize(ctx, len(body)) + + log.Info("Raw RPC request", + "body", truncate(string(body)), + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + ) + + if IsBatch(body) { + reqs, err := ParseBatchRPCReq(body) + if err != nil { + log.Error("error parsing batch RPC request", "err", err) + RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) + writeRPCError(ctx, w, nil, ErrParseErr) + return + } + + if len(reqs) > MaxBatchRPCCalls { + RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrTooManyBatchRequests) + writeRPCError(ctx, w, nil, ErrTooManyBatchRequests) + return + } + + if len(reqs) == 0 { + writeRPCError(ctx, w, nil, ErrInvalidRequest("must specify at least one batch call")) + return + } + + batchRes, batchContainsCached, err := s.handleBatchRPC(ctx, reqs, true) + if err == context.DeadlineExceeded { + writeRPCError(ctx, w, nil, ErrGatewayTimeout) + return + } + if err != nil { + writeRPCError(ctx, w, nil, ErrInternal) + return + } + + setCacheHeader(w, batchContainsCached) + writeBatchRPCRes(ctx, w, batchRes) + return + } + + rawBody := json.RawMessage(body) + backendRes, cached, err := s.handleBatchRPC(ctx, []json.RawMessage{rawBody}, false) + if err != nil { + writeRPCError(ctx, w, nil, ErrInternal) + return + } + setCacheHeader(w, cached) + writeRPCRes(ctx, w, backendRes[0]) +} + +func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isBatch bool) ([]*RPCRes, bool, error) { + // A request set is transformed into groups of batches. + // Each batch group maps to a forwarded JSON-RPC batch request (subject to maxUpstreamBatchSize constraints) + // A groupID is used to decouple Requests that have duplicate ID so they're not part of the same batch that's + // forwarded to the backend. This is done to ensure that the order of JSON-RPC Responses match the Request order + // as the backend MAY return Responses out of order. + // NOTE: Duplicate request ids induces 1-sized JSON-RPC batches + type batchGroup struct { + groupID int + backendGroup string + } + + responses := make([]*RPCRes, len(reqs)) + batches := make(map[batchGroup][]batchElem) + ids := make(map[string]int, len(reqs)) + + for i := range reqs { + parsedReq, err := ParseRPCReq(reqs[i]) + if err != nil { + log.Info("error parsing RPC call", "source", "rpc", "err", err) + responses[i] = NewRPCErrorRes(nil, err) + continue + } + + if err := ValidateRPCReq(parsedReq); err != nil { + RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) + responses[i] = NewRPCErrorRes(nil, err) + continue + } + + group := s.rpcMethodMappings[parsedReq.Method] + if group == "" { + // use unknown below to prevent DOS vector that fills up memory + // with arbitrary method names. + log.Info( + "blocked request for non-whitelisted method", + "source", "rpc", + "req_id", GetReqID(ctx), + "method", parsedReq.Method, + ) + RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrMethodNotWhitelisted) + responses[i] = NewRPCErrorRes(parsedReq.ID, ErrMethodNotWhitelisted) + continue + } + + id := string(parsedReq.ID) + // If this is a duplicate Request ID, move the Request to a new batchGroup + ids[id]++ + batchGroupID := ids[id] + batchGroup := batchGroup{groupID: batchGroupID, backendGroup: group} + batches[batchGroup] = append(batches[batchGroup], batchElem{parsedReq, i}) + } + + var cached bool + for group, batch := range batches { + var cacheMisses []batchElem + + for _, req := range batch { + backendRes, _ := s.cache.GetRPC(ctx, req.Req) + if backendRes != nil { + responses[req.Index] = backendRes + cached = true + } else { + cacheMisses = append(cacheMisses, req) + } + } + + // Create minibatches - each minibatch must be no larger than the maxUpstreamBatchSize + numBatches := int(math.Ceil(float64(len(cacheMisses)) / float64(s.maxUpstreamBatchSize))) + for i := 0; i < numBatches; i++ { + if ctx.Err() == context.DeadlineExceeded { + log.Info("short-circuiting batch RPC", + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + "batch_index", i, + ) + batchRPCShortCircuitsTotal.Inc() + return nil, false, context.DeadlineExceeded + } + + start := i * s.maxUpstreamBatchSize + end := int(math.Min(float64(start+s.maxUpstreamBatchSize), float64(len(cacheMisses)))) + elems := cacheMisses[start:end] + res, err := s.backendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) + if err != nil { + log.Error( + "error forwarding RPC batch", + "batch_size", len(elems), + "backend_group", group, + "err", err, + ) + res = nil + for _, elem := range elems { + res = append(res, NewRPCErrorRes(elem.Req.ID, err)) + } + } + + for i := range elems { + responses[elems[i].Index] = res[i] + + // TODO(inphi): batch put these + if res[i].Error == nil && res[i].Result != nil { + if err := s.cache.PutRPC(ctx, elems[i].Req, res[i]); err != nil { + log.Warn( + "cache put error", + "req_id", GetReqID(ctx), + "err", err, + ) + } + } + } + } + } + + return responses, cached, nil +} + +func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { + ctx := s.populateContext(w, r) + if ctx == nil { + return + } + + log.Info("received WS connection", "req_id", GetReqID(ctx)) + + clientConn, err := s.upgrader.Upgrade(w, r, nil) + if err != nil { + log.Error("error upgrading client conn", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) + return + } + + proxier, err := s.wsBackendGroup.ProxyWS(ctx, clientConn, s.wsMethodWhitelist) + if err != nil { + if errors.Is(err, ErrNoBackends) { + RecordUnserviceableRequest(ctx, RPCRequestSourceWS) + } + log.Error("error dialing ws backend", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) + clientConn.Close() + return + } + + activeClientWsConnsGauge.WithLabelValues(GetAuthCtx(ctx)).Inc() + go func() { + // Below call blocks so run it in a goroutine. + if err := proxier.Proxy(ctx); err != nil { + log.Error("error proxying websocket", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) + } + activeClientWsConnsGauge.WithLabelValues(GetAuthCtx(ctx)).Dec() + }() + + log.Info("accepted WS connection", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx)) +} + +func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context.Context { + vars := mux.Vars(r) + authorization := vars["authorization"] + + if s.authenticatedPaths == nil { + // handle the edge case where auth is disabled + // but someone sends in an auth key anyway + if authorization != "" { + log.Info("blocked authenticated request against unauthenticated proxy") + httpResponseCodesTotal.WithLabelValues("404").Inc() + w.WriteHeader(404) + return nil + } + return context.WithValue( + r.Context(), + ContextKeyReqID, // nolint:staticcheck + randStr(10), + ) + } + + if authorization == "" || s.authenticatedPaths[authorization] == "" { + log.Info("blocked unauthorized request", "authorization", authorization) + httpResponseCodesTotal.WithLabelValues("401").Inc() + w.WriteHeader(401) + return nil + } + + xff := r.Header.Get("X-Forwarded-For") + if xff == "" { + ipPort := strings.Split(r.RemoteAddr, ":") + if len(ipPort) == 2 { + xff = ipPort[0] + } + } + + ctx := context.WithValue(r.Context(), ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck + ctx = context.WithValue(ctx, ContextKeyXForwardedFor, xff) // nolint:staticcheck + return context.WithValue( + ctx, + ContextKeyReqID, // nolint:staticcheck + randStr(10), + ) +} + +func setCacheHeader(w http.ResponseWriter, cached bool) { + if cached { + w.Header().Set(cacheStatusHdr, "HIT") + } else { + w.Header().Set(cacheStatusHdr, "MISS") + } +} + +func writeRPCError(ctx context.Context, w http.ResponseWriter, id json.RawMessage, err error) { + var res *RPCRes + if r, ok := err.(*RPCErr); ok { + res = NewRPCErrorRes(id, r) + } else { + res = NewRPCErrorRes(id, ErrInternal) + } + writeRPCRes(ctx, w, res) +} + +func writeRPCRes(ctx context.Context, w http.ResponseWriter, res *RPCRes) { + statusCode := 200 + if res.IsError() && res.Error.HTTPErrorCode != 0 { + statusCode = res.Error.HTTPErrorCode + } + + w.Header().Set("content-type", "application/json") + w.WriteHeader(statusCode) + ww := &recordLenWriter{Writer: w} + enc := json.NewEncoder(ww) + if err := enc.Encode(res); err != nil { + log.Error("error writing rpc response", "err", err) + RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) + return + } + httpResponseCodesTotal.WithLabelValues(strconv.Itoa(statusCode)).Inc() + RecordResponsePayloadSize(ctx, ww.Len) +} + +func writeBatchRPCRes(ctx context.Context, w http.ResponseWriter, res []*RPCRes) { + w.Header().Set("content-type", "application/json") + w.WriteHeader(200) + ww := &recordLenWriter{Writer: w} + enc := json.NewEncoder(ww) + if err := enc.Encode(res); err != nil { + log.Error("error writing batch rpc response", "err", err) + RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) + return + } + RecordResponsePayloadSize(ctx, ww.Len) +} + +func instrumentedHdlr(h http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + respTimer := prometheus.NewTimer(httpRequestDurationSumm) + h.ServeHTTP(w, r) + respTimer.ObserveDuration() + } +} + +func GetAuthCtx(ctx context.Context) string { + authUser, ok := ctx.Value(ContextKeyAuth).(string) + if !ok { + return "none" + } + + return authUser +} + +func GetReqID(ctx context.Context) string { + reqId, ok := ctx.Value(ContextKeyReqID).(string) + if !ok { + return "" + } + return reqId +} + +func GetXForwardedFor(ctx context.Context) string { + xff, ok := ctx.Value(ContextKeyXForwardedFor).(string) + if !ok { + return "" + } + return xff +} + +type recordLenWriter struct { + io.Writer + Len int +} + +func (w *recordLenWriter) Write(p []byte) (n int, err error) { + n, err = w.Writer.Write(p) + w.Len += n + return +} + +type NoopRPCCache struct{} + +func (n *NoopRPCCache) GetRPC(context.Context, *RPCReq) (*RPCRes, error) { + return nil, nil +} + +func (n *NoopRPCCache) PutRPC(context.Context, *RPCReq, *RPCRes) error { + return nil +} + +func truncate(str string) string { + if len(str) > maxLogLength { + return str[:maxLogLength] + "..." + } else { + return str + } +} + +type batchElem struct { + Req *RPCReq + Index int +} + +func createBatchRequest(elems []batchElem) []*RPCReq { + batch := make([]*RPCReq, len(elems)) + for i := range elems { + batch[i] = elems[i].Req + } + return batch +} diff --git a/proxyd/proxyd/string_set.go b/proxyd/proxyd/string_set.go new file mode 100644 index 0000000..4582349 --- /dev/null +++ b/proxyd/proxyd/string_set.go @@ -0,0 +1,56 @@ +package proxyd + +import "sync" + +type StringSet struct { + underlying map[string]bool + mtx sync.RWMutex +} + +func NewStringSet() *StringSet { + return &StringSet{ + underlying: make(map[string]bool), + } +} + +func NewStringSetFromStrings(in []string) *StringSet { + underlying := make(map[string]bool) + for _, str := range in { + underlying[str] = true + } + return &StringSet{ + underlying: underlying, + } +} + +func (s *StringSet) Has(test string) bool { + s.mtx.RLock() + defer s.mtx.RUnlock() + return s.underlying[test] +} + +func (s *StringSet) Add(str string) { + s.mtx.Lock() + defer s.mtx.Unlock() + s.underlying[str] = true +} + +func (s *StringSet) Entries() []string { + s.mtx.RLock() + defer s.mtx.RUnlock() + out := make([]string, len(s.underlying)) + var i int + for entry := range s.underlying { + out[i] = entry + i++ + } + return out +} + +func (s *StringSet) Extend(in []string) *StringSet { + out := NewStringSetFromStrings(in) + for k := range s.underlying { + out.Add(k) + } + return out +} diff --git a/proxyd/proxyd/tls.go b/proxyd/proxyd/tls.go new file mode 100644 index 0000000..34b214c --- /dev/null +++ b/proxyd/proxyd/tls.go @@ -0,0 +1,33 @@ +package proxyd + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "io/ioutil" +) + +func CreateTLSClient(ca string) (*tls.Config, error) { + pem, err := ioutil.ReadFile(ca) + if err != nil { + return nil, wrapErr(err, "error reading CA") + } + + roots := x509.NewCertPool() + ok := roots.AppendCertsFromPEM(pem) + if !ok { + return nil, errors.New("error parsing TLS client cert") + } + + return &tls.Config{ + RootCAs: roots, + }, nil +} + +func ParseKeyPair(crt, key string) (tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair(crt, key) + if err != nil { + return tls.Certificate{}, wrapErr(err, "error loading x509 key pair") + } + return cert, nil +} From 038c936db5d82ba1591d7714c1147f755ff74a91 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 May 2022 18:26:32 +0000 Subject: [PATCH 002/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 6 ++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 677dd13..4623bb0 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,11 @@ # @eth-optimism/proxyd +## 3.8.6 + +### Patch Changes + +- d79d40c4: proxyd: Proxy requests using batch JSON-RPC + ## 3.8.5 ### Patch Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index dbdbead..3087ed4 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.8.5", + "version": "3.8.6", "private": true, "dependencies": {} } From 5019c2f69f0a8ce6c45ba3966c69a08d1708b40f Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Mon, 16 May 2022 15:43:01 -0600 Subject: [PATCH 003/212] Bedrock -> Develop (#2563) * opnode: Reconcile epochs and handle reorgs (#309) * opnode: Switch to uint64 in FindL2Heads function * opnode: Properly initialize state loop This properly sets the L1 head, L2 Head, and L2 Unsafe Head during `state.Start()` in normal operation and when the current L1 head has not reached the L1 genesis. * opnode: Rename output functions The old names where not good. In addition, this standardizes the order of arguements and extends the returns. The returns are not fully used, but will be. * opnode: Add sanity check to L1 window in insertEpoch This make sure that the L1 window starts at the correct block. * opnode: Split out loop Now each action occurs in a seperate function. The state loop is still responsible for sequencing follow-up actions, but is not responsible for setting state. * opnode: Better name for batch prepare functions This name indicates that it provides missing batches. * opnode: Verify epochs as sequencer Sequencers now verify epochs and will reorg if they see that verified epochs do not match what they created. * opnode: Mute driver test * opnode: Set L2 Geth forkchoice on reorg This is required to handle L1 reorgs (rather than just missing batches). * opnode: Bump op-geth version Needed to fix a bug where the L2 geth node was missing the `mint` field in deposit transactions when returning them over the JSON RPC server. * opnode: Don't incorrectly advance safehead There is some L2 block that is possible to fully derive from L1; however, if it is ahead of the current state's safe head, do not advance the current state's safehead because the chain has not been verified up to that point. * opnode: Don't rebuild L1 Genesis Block ID Co-authored-by: Diederik Loerakker * opnode: More comments Co-authored-by: Diederik Loerakker * opnode: Don't cast slice Co-authored-by: Diederik Loerakker * opnode: PR comments * opnode: Better error reason in attributesMatch Include the details of the mismatched fields in addition to what field it was. Will make diagnosing errors much easier. Co-authored-by: Diederik Loerakker * go.mod: update to squashed l2 geth diff d5e1fc1a74bda3cbe5715d5732621460f9e00908 * opnode/rollup: use L2 block height in L1-info deposit tx block-height field (tx field, not info field) for uniqueness of deposit tx hash on L2 * feat: DepositFeed receive function It is convenient for users to be able to deposit funds directly to L2 by simply doing an ETH send to an address. The prevents needing an ABI. * Update slither db * Add solidity test on df.receive() function * Add warning message on DepositFeed.receive() * specs: Add Withdrawals specification * contracts: Add withdrawor and start of tests * ops: Add withdrawor predeploy to docker setup ops: Add contract code to Makefile and Dockerfile * Add gaslimit to withdrawal encoding * test(contracts): Withdrawal creation and proof gen * deps: install entire goddamn monorepo using forge. I needed the trie contracts over here, so now the whole monorepo is a submodule of the specs repo. Happy now @norswap? Why, you ask, did I do this rather than the alternatives available to me? 1. Copying and pasting is gross. 2. `yarn add` would have installed a smaller dep, but it's also gross. 3. git submodules, while also gross in their own right, is at least consistent with how we're currently handling other dependencies, so I went with that. * Add output root inclusion proof Adds a check to ensure that withdrawal root is included in the l2 output root. * Add storage inclusion proof Adds a check to ensure that the withdrawal message hash is included in the mapping of withdrawals in the withdrawer contract * Improve testing against L2 node * Add finalizationWindow var to WithdrawalVerifier * Add WithdrawalVerified event * Remove unused constructor argument * Clean up Forge test of WithdrawalVerifier * ops: Docker get l2 genesis hash on startup Docker fixes * contracts: ignore dirty submodules * Don't run hardhat tests in CI * Create Optimism Portal contract * Move receive function to OptimismPortal * specs: Add overview page (#286) * Convert WithdrawalVerifier to a library This changes the WithdrawalVerifier to be stateless, and moves all checks into the OptimismPortal * contracts: Rename Withdrawor to Withdrawer * specs: define timestamp bounds of derivation process, clear up block derivation, define missing noTxPool field * opnode/rollup: implement new timestamp bounds on block derivation batch filtering and filling * opnode: fix sequencer L1 origin advancing, and fix e2e genesis time * contracts: Add burn function to the Withdrawer contract * contracts: Add natspec comments * contracts: Remove timestamp from OutputRootProof * contracts: undoL1ToL2Alias on withdrawals from a contract Also uses AddressAliasHelper lib for aliasing instead of reimplementing it. * specs/rollup-node: improve spec based on review suggestions from @norswap * opnode/rollup: implement review suggestions from @trianglesphere * specs/rollup-node: document timestamp invariant between L2 and L1 origin * opnode/rollup/driver: continue sequencing on current epoch if next L1 origin timestamp is not reached yet * opnode/rollup: fix maxL2Time excl bound, and return adjusted maxL2time in long L1 gap case * contracts: Add withdrawer tests fixup! contracts: Add burn function to the Withdrawer contract * Rename Finalization Window to Finalization Period * contracts: Validate output root proof in portal Moves the comparison into the Portal contract, and changes the WithdrawalVerifier function from _verifyWithdrawerStorageRoot to _deriveOutputRoot. * contracts: Use solidity custom errors * contracts: Use unchecked math when safe * contracts: Add section dividers to contract layout * specs: Update withdrawals spec to match impl * contracts: Fix ETH burning in the withdrawer contract * specs: Address review comments * ops: Address review comments * Update Dockerfile.opnode * ops: Fix devnet - Fixes the devnet config to correctly generate rollup/genesis files. - Fixes a bug that causes the rollup node to get stuck when there's a large timestamp delta between genesis and the current time, as is the case in the devnet. * Update opnode/rollup/driver/state.go Co-authored-by: Diederik Loerakker * Update rollup.json * Update predeploy addresses The new predeploy addresses are incremented by 1 in order to avoid conflicts with the system addresses. * tests: Add tests for batch requests when the parent request fails This test makes sure that we do not retry when the parent request fails. We don't, but it wasn't immediately clear. I also removed a duplicate assertion lib since we're using `testify` everywhere else. * opnode: Don't start sequencing blocks until past the L1 genesis (#325) This has to be reverted to ensure that the sequencer starts producing blocks at the same location as the verifier. Sometimes the previous code would cause reorgs on the sequencer (expected), and sometimes it would cause a chain split (unexpected and unexplained as of writing this commit message 4/6/22). * opnode: update to latest L2 geth * readme: fix typos and break lines (#327) * Update ref optimistic geth image (#328) * ci: Improve timestamp management in devnet (#329) This change sets timestamps to the current timestamp on fresh devnet deploys. This avoids the devnet having to fill in a bunch of blocks on startup. The timestamp is preserved between runs; `make devnet-clean` will erase the timestamp data if you want to start anew. * contracts: Move withdrawal logic into abstract contract * contracts: Move withdrawal hashing logic into library * contracts: Update .gas-snapshot * contracts: silence compiler warnings * feat: expose debug http api (#334) * opnode: Use l2 safe head instead of l2 head Epochs should be inserted on top of the L2 Safe Head, not the unsafe head. This was causing problems when a reorg would occur and the node would start building in the wrong spot. * opnode: Clear l1WindowBuf on insertEpoch failure This is done to handle the case that the l1WindowBuf does not start in the correct location (direct check is difficult with context). The correct response to that is to clear the window and initialize it from the correct block. * hardhat: clean up the hardhat deposit task * fix: oe => optimism (#338) * opnode: Rework state test to build on safe head This also got caught up in the unsafe/safe head confusion. * opnode: fix setL1BlockValues selector used for deposits (#339) Solidity ABI only looks at the function name and types, but does not consider the name of the function parameters. * rollup: fix address of the L1 Block Attributes contracts * fix: typo in l1InfoPredeployAddress * fix: gofmt and goimports CI was asking for goimport'd when all it needed was gofmt. * opnode: Import constants * ops: fix formatting, add docker buildkit support * ops: prune volumes on devnet-down * fix: replace fraud with fault (#337) * opnode: Fix bug in block creation (#345) The NoTxPool flag was incorrectly set to falses in the InsertEpoch function. * opnode: Don't allow arbitrary mints (#346) The ordering of `mint, value` was switched to be `value, mint` when parsing emitted logs. This allows users to control their mint rather than using `msg.value` which is being fed directly into the event. Thanks to Mofi (Inphi) for finding this. * opnode,specs: deposit tx-hash uniqueness based on L1 * opnode/l1: verify critical receipt/log info we get from RPC * opnode/test: reconstruct deposit hash in e2e test, fetch l2 deposit receipt * opnode/l1: fix panic on channel send after close, sema channel can stay open * go.mod: l2 geth update with improved deposit tx hash * README: remove alpha branch + update description (#332) * allow building everything from top-level makefile * allow testing from top level makefile * Update Makefile Co-authored-by: Joshua Gutow * opnode/l1: wrap errors and fmt test imports * opnode: don't expose types.Block to derive or driver packages, prefer l2.ExecutionPayload * opnode: add comments, fix genesis base case to get l1 origin * init: integration tests (#344) Co-authored-by: Javed Khan * opnode: Fix possible panic in sequencer batch creation (#342) Previously we assumed that every deposit would be included in the L2 block. However, the L2 execution engine removes depoists that are not valid. As such the number of deposits submitted to the execution engine could be greater than the number of transactions returned causing a panic. This fixes the issue by counting the number of deposits returned and using that to slice the payload transactions for batch submission. (Deposits are not submitted in batches and instead read directly from L1). This also does more sanity checks on the transactions included in the L2 block and rejects blocks with invalid deposit tx configurations. This is done prior to marking the payload canonical with the FCU. * opnode: Test Utilities (#319) * opnode: Deduplicate fake chain source The fake chain source is an easy way to create and L1 and L2 chains from a simple string defition. There were previously two implementations and now they are in a shared testutils package. * opnode: Dedicated wait functions This deduplicates wait for transactions on L1 and L2. * opnode: New e2e test system This splits out the initialization from the actual tests to eanble multiple e2e tests. Some of the config is still not as dedpulicated as I would like, but it is more centralized than it previously was. Aims to move the setup from the tests and enable easier e2e tests. * opnode: Silence FindSyncStartTest * opnode: Add test for missing batches This ensures the the sequencer follows the chain that is derived from L1 rather than what it sequenced in the case that it misses batch submission. * opnode: Pull out system config to default cfg This reduces duplication and makes tests easier to understand. * opnode: Address PR comments * opnode: Reduce flakiness in missing batch test The proper way to do this is to wait for a safe block, but the API is not yet ready for that. * Fix sync-start bug + improve sync-start comments (#355) Fix bug (L1 origin head with non-canonical ancestors) This happened in the scenario where `l2ahead == true` (L1 origin of start is ahead of know L1 head) and `start` had an ancestor whose L1 origin was known not to be canonical. Example: L1 head is at block X. `start` has an L1 origin with number X + 1 but its parent L2 block has an L1 origin with number X whose blockhash is not canonical. In this case, the algorithm still returned `start` as the unsafe L1 head, which is incorrect. This also touches a bunch of comments to generally improve understanbility. * feat: deposit tx js helpers (#352) * feat: js deposit tx Hardhat task now lists the tx hash * rollup: new deposit hashing * nice api! * fix: dockerfile * devnet: Hardcode deposit feed bytecode (#359) * opnode,l2os: Better lifecycle management (#358) * opnode: Properly wait for limit client to shut down We removed closing the semaphore channel because it would cause a panic as we did not wait for in flight requests (and new requests) to stop before closing the L1 source. * l2os: Change shutdown procedure This is now blocking, but also reuces the amount of error logs. * opnode: Shutdown rollup nodes before clients/geth nodes Also actually shuts down the L2 clients in the rollup node. * opnode: Properly shut everything down * style: clean up and comments for state.go (#356) * opnode: Parameterize deposit contract address (#361) The contract can be deployed to any address. It is set in the rollup config file. * opnode/test: Add reverted deposit with mint test * fix(ci): use Foundry GHA (#367) Nightly Foundry releases are kept around for 3 days. This means we cannot be pinning the download link on CI to a specific commit. Instead, we use the Foundry Toolchain GHA which always uses the latest nightly version. * opnode/test: check nonce increment in deposit fail * integration-tests: End-to-end withdrawal test (#362) Includes several fixes to make this work: - Modifies the devnet to deploy the L1 contracts rather than include them in genesis. - Changes the deployment of L2OutputOracle to support a custom start timestamp. - Updates UserDeposits to properly filter out non-TransactionDeposited events. - Updates the L2 output submitter to post the current L1 block with each output rather than the L2 checkpoint block - Updates the opnode API to allow all vhosts and CORS domains. * go.mod: bump l2geth version * opnode: Update and deploy contracts (#360) This is rather than hardcoding the contract bytecode. It slows down the e2e tests (by having to wait 1 L1 block for contracts to be ready), but it is more accurate to what occurs (and is now required as we initialize actions). This also pins the L2OutputOracle to v0.8.10 of solidity. * specs: Update output root derivation (#357) * specs: Update output root derivation * specs: Separate version from payload * opnode: libp2p setup * p2p flags, config, and libp2p + discv5 setup * specs: initial rollup node p2p spec * specs: Fix definition of output root * solidity: fail if updated gas snapshot isn't included A new gas snapshot can be generated by running `forge snapshot` * p2p setup: implement review suggestions, fix toc, fix lint * opnode/p2p: test with require instead of assert * contracts: Fix reentrancy attack in WithdrawalsRelay (#378) * contracts: Output oracle improvements (#370) * contracts: Use correct uppercase for immutables * itest: shorten submission interval for faster devnet tests * contracts: Ensure submission interval is multiple of block time * itest: Simplify withdrawals spec * itest: Increase withdrawal finalization timeout * itest: Bump withdrawal timeout to +5 minutes * Bump timeout * Bump again * Add gas snap Co-authored-by: Matthew Slipper * Clean up withdrawals itest (#372) * contracts: Use correct uppercase for immutables * itest: shorten submission interval for faster devnet tests * contracts: Ensure submission interval is multiple of block time * itest: Simplify withdrawals spec * itest: Increase withdrawal finalization timeout * itest: Bump withdrawal timeout to +5 minutes * itest: Extract logic into getTargetOutput * Update 000_withdrawals.spec.ts Co-authored-by: Matthew Slipper * fix dead anchor link * higher difficulty rather than number * 'L1 attributes block' is not a thing + add link * try to align sentences with lines * ci: Add automated docker builds for opnode and l2 output submitter (#369) * contracts: Add deleteL2Output function * specs: Add deleteL2Output * ci: Update Docker version in CircleCi (#381) We ran into this on the monorepo too. Alpine 3.14 removes the `faccessat2` syscall. This caused "operation not permitted" errors while building. Upgrading Circle's Docker version fixes this. See https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.14.0#faccessat2 for more information. * contracts: reuse code (#380) * contracts: reuse the library to compute withdrawal hash The `WithdrawalVerifier` has a method to compute the withdrawal hash as part of a library. Use this library in both the L1 and L2 contracts so that code can be reused. * contracts: gas snapshot * opnode: Better parsing of the TransactionDepositedEvent (#382) This now asserts that the offset for the bytes field of the event is the correct value rather than the previous incorrect check. This check is not fully necessary, but good to have to validate that the data is well formed. * Makefile,ops: docker compose up instead of run to make devnet-down work, and fix makefile label (#385) * opnode: Do not drop all deposits on parsing error (#383) * Change deposit gasLimit from uint256 to uint64 This prevents the user from setting a gas limit that the rollup node is not able to parse. * opnode: Do not drop all deposits on parsing error Only drop the affected deposit. This does mean loss of funds, but this is also a case that should never happen. In addition, halting the chain means that while funds are safe, we are exposed to a denial of service attack if it would be possible to cause a parsing error from the deposit feed contract. * opnode/test: Provide flag to access geth logs (#389) This enables a more verbose output without having to modify the test itself. Provide the flag `gethlogs` to see logs from geth. * opnode: Fix L1 Info Transactions There are three issues being fixed: 1. Set the `from` field to the correct magic value (found by ToB) 2. Update the L2 EE to provide gas to deposits. Otherwise the deposit transactions immediatley out of gas and fail. 3. Update the manual ABI encoding to match what solidity expects. The previous version was similar to packed encodeding, but as such could not be parsed by solidity. This also includes a regression test that the parsing of the L1 info tx and what is recorded in the state for each block matches. * contracts: import messengers (#393) * chore: copy L1 and L2 Messengers from Monorepo * test: Add Messenger test files Monorepo test cases are copied in as comments from the monorepo ts tests. * chore: Add OZ upgradable contracts New remappings were also added in order to avoid excessively long import statements. * chore: Importe OZ and OP contracts as node_modules Necessary because hardhat does not support remappings!?! y u no? * refactor: Remove replayMessage() We no longer need this function as it was only necessary when the CTC a maximum gas limit per epoch concept. In order to remove the function I had to copy in the L1xDM interface rather than import from the node_modules. * chore: Remove unused files The ts test file is made redundant by an itest in the itest package. The sol test file was not being used for anything * refactor: Remove Address Manager and Resolver Instead the CTC and SCC are state variables * refactor: Replace CTC with OptimismPortal * refactor: Remove SCC We don't need to replace it with the L2OutputOracle in the L1xDM, because the verification is now done in the OptimismPortal itself. * forge install: forge-std * forge install: ds-test * refactor: Move boilerplate test code into CommonTest * test: Add sendMessage and pause tests for L1xDM * test: L1CrossDomainMessenger sendMessage and pause * refactor: replace L2ToL1MessagePasser with Withdrawer contract Also adds a lib with constant values for new bedrock predeploys. * chore: Make functions external, and reorder for CEI For whatever reason a bunch of functions on the messengers were public, when they could have been external. I fixed that, and removed the slither annotations. Where possible (in the sendMessage functions), I also reordered the events and calls to respect Checks-Effects-Interactions. There was no risk previously, but this removes any question, and quiets slither. * refactor: Reorganize ts helpers Move helpers/index into utils.ts, and add other files which are exported in the new index.ts. * test: Add mock proof generation script and helpers * test: Add L1xDM relayMessageSucceeds tests * test: Add proof generation scripts and helpers * refactor: Add l2Sender check in L2xDM * refactor: Copy in the L1 and L2 standard bridge At this point they are simply verbatim. * refactor: Bridges - fix import paths * refactor: Token Bridge - replace messengers with Portal Also remove the CrossDomainEnabled lib. refactor: Token Bridge - replace messengers with Portal Also remove the CrossDomainEnabled lib. * style: Address/remove some slither disable comments style: Address/remove some slither disable comments * refactor: extract l2Sender check into a modifier * refactor: Support deposits of ETH in L2 Bridge This copies in the IL2ERC20Bridge interface so that payable can be added. In the case that the L2 token address matches OVM_ETH, the value of the call will be forwarded. * interface: add IWithdrawer.sol * contracts: add comments to L1 contracts * contracts: use unchecked * contracts: fix imports in common test * test: L1CrossDomainMessenger * test: L2CrossDomainMessenger * contracts: fix typo * tests: bridge tests * contracts: remove extra message assignment * contracts: update gas snapshot * forge install: solmate * contracts: remove usage of OVM_ETH All `OVM_ETH` will be migrated to `ETH` with the upgrade to bedrock. We do not want to allow for the creation of new `OVM_ETH` by depositing `ETH` into the bridge and have it create `OVM_ETH` on L2. * contracts: add in L2StandardERC20 * contracts: add in token factory The token factory will deploy tokens on L2 that correspond to tokens on L1. This allows for easy deposits through the bridge. * contracts: test rlp lib for computing contract addrs This library lets you compute the contract address based on the deployment account and nonce. h/t @t11s * contracts: test infra for bridge * contracts: add note to self * contracts: fix compiler warnings * contracts: update snapshot * contracts: add IDepositFeed * contracts: type cast uint256 to uint64 in messenger * test: fix merge * contracts: modify paths to compile with hardhat * hardhat: update config * contracts: fix build * forge tests: first yarn install * contracts: lint * contracts: update snapshot Co-authored-by: Maurelian * contracts: fix abi encoding in proof verification (#395) Use `abi.encode` instead of `abi.encodePacked` to ensure a constant serialization. `abi.encode` will be sure to pad the value to its size while `abi.encodePacked` will not when operating on integers. There should not have been a bug here because it was being called with a `bytes32`, which should always be 32 bytes when returned from `abi.encodePacked`. * contracts: don't unalias on L2->L1 Co-authored-by: smartcontracts * opnode: attach p2p host and discovery to rollup node, update e2e test (#388) * deps: update forge-std * contracts: fix build * build: fix bindings build * fix: builds * forge install: ds-test * build: fix for good * l2os: update bindings * opnode: add seq nr to l1-info to fix driver bug * feat: create opnode/predeploy package This resolves an import cycle when trying to use WithdrawalContractAddress in opnode/node. Otherwise the compiler complains that opnode imports opnode/node which imports opnode. * opnode: refactor RPC server / config, prototype batch serving * feat: enforce size contraints on batch bundles This algorithm is modeled mostly off the one used in the existing batch submitter. The additional complexity enables us to configure a min and max value such that max - min < max_l2_tx_size. Applied strictly, submitting a large L2 tx would cause the batch construct to halt, since the batch constructed without including batch `i` can be under the min, while including batch `i` resulting in exceeding the maximum. The additional complexity in the pruning algorithm is used to identify this case and submit an undersized batch when it cannot be avoided. In the general case, however, this strategy increases our expected profitibability since the average batch size is closer to the desired maximum. * feat: add BundleBuilder helper class * fix: typo in l2os config docs * feat: add GetBatchBundle method to rollupclient.RollupClient * feat: simplify l2os rollupclient init * feat: reduce BSS poll interval The current setup assumes the batches are submitted very soon after the block is created on L2. Increasing the poll interval ensures that we discover and publish new batches in response. Without modifying this value, the subsequent changes will still fail even though the code is correct. * feat: add sequencer history db * feat: add sequencer BSS driver to submit batches * feat: add bss configuration * feat: add LOG_TERMINAL flag to l2os, use non-global log instance Also modifies the shutdown logic of l2os.Service to abort if stuck publishing a transaction. * feat: enable standalone bss, disable simple bss * feat: remove simple bss * feat: add bss to devnet * fix: ci build bindings (#406) * ci: build bindings in ci * ci: install abigen * ci: fix geth install * bindings: regenerate * contracts: no metadata hash in contracts * bindings: regenerate * opnode: Avoid busy-waiting while L2 head is behind L1 In the state select loop, we immediately request for new L2 blocks whenever the latest L1 origin is behind L1. This can be an issue as we attempt to re-request the latest L2 block without any delay. This is a DoS hazard particularly when an L2 block cannot be retrieved because either the `L1Chain` or `L2Chain` backends have errors. Another problem with this is that other, more useful events in the state select loop, are less likely to be scheduled by the Go runtime due to the busy-wait. Adding a small delay, before calling `reql2BlockCreation` of about 10ms should be enough to prevent issues. * opnode: Fix Incorrect error handling when creating an L2 block (#391) * opnode: Fix Incorrect error handling when creating an L2 block This is an issue ToB identified (issue #8). What happens is that the ethereum.NotFound error would never be returned, but in general if there is an error in the lookup, the state loop should keep going. * opnode: Properly wrap errors in l1/source.go * opnode: Fuzz manual ABI parsing (#384) * opnode: Add OptimismPortal deployed bytecode This is important for fuzzing * opnode: Add differential fuzzing test for deposit events fixup: Proper tests * build: Add fuzz target * opnode: Fix ineffctual error assignment * opnode: More fuzzing cleanup The purpose of this is to make it more likely that the deposit succeeds and that we fuzz what we actually want to fuzz (the parsing). * fix: DepositFeed.sol link * opnode: gossip blocks topic validator and subscriber * opnode/l2: ExecutionPayload SSZ encoding/decoding * opnode/l2: check payload block hash consistency * opnode/p2p,specs: update p2p block gossip validation * opnode: refactor node initialization, change l1-head fan out, implement l2 payload driver receiver, setup block signing, update config / cli / e2e * opnode/l2,opnode/rollup/driver: process incoming unsafe L2 blocks * specs/rollup-node-p2p: fix toc * opnode/p2p: compress published messages * specs/rollup-node-p2p: clarify hardfork version start * opnode/node: fix l1 head notify timeout bug * opnode: fix receive p2p payload timeout bug, fix publishing buffer missing reset * opnode: fix/improve new loggers * opnode: fix p2p block distribution * opnode/test: connect peers only after starting p2p application-layer protocols * ops,opnode/p2p: fix opnode start up in devnet * opnode/l2: use common.Big0 instead of big.NewInt(0), thx mark * opnode: Add debug tracer to geth in e2e test (#418) * Update reference geth entrypoint with variable parameters * opnode: fix imports and code newlines style * opnode,specs: gossip blocks validation seen cache fixes * opnode/rollup/driver: no heuristics in driver, rely more on engine to handle reorgs * opnode: tracer to watch node events during testing * opnode,specs: clarify gossip params * opnode/l2/ssz: fix lousy copy range * opnode/rollup/driver/state: fix doc typo Co-authored-by: Joshua Gutow * Add version RPC (#432) Meta: - Fixes ENG-2200 * refactor: new messengers (#421) * contracts: consolidate OptimismPortal Co-authored-by: smartcontracts * contracts: delete abstracts Co-authored-by: smartcontracts * contracts: base `CrossDomainMessenger` + L1 and L2 Implement the `L1CrossDomainMessenger` and `L2CrossDomainMessenger` based on the base `CrossDomainMessenger`. This makes the interfaces the same on both sides. Co-authored-by: smartcontracts * contracts: `StandardBridge` Also implement the `L1StandardBridge` and `L2StandardBridge` based off of the base `StandardBridge` Co-authored-by: smartcontracts * contracts: L2OutputOracle Co-authored-by: smartcontracts * contracts: standard bridge tests Co-authored-by: smartcontracts * contracts: cross domain hashing lib + tests Co-authored-by: smartcontracts * contracts: tests for cross domain messengers Co-authored-by: smartcontracts * contracts: L2ToL1MessagePasser Co-authored-by: smartcontracts * contracts: safe call lib Co-authored-by: smartcontracts * contracts: optimism mintable erc20 Co-authored-by: smartcontracts * contracts: common test setup Co-authored-by: smartcontracts * contracts: optimism mintable token factory Co-authored-by: smartcontracts * contracts: update forge-std Co-authored-by: smartcontracts * contracts: test OptimismPortal Co-authored-by: smartcontracts * contracts: L1Block attributes Co-authored-by: smartcontracts * contracts: update libraries Co-authored-by: smartcontracts * contracts: update foundry.toml Co-authored-by: smartcontracts * contracts: Burner Co-authored-by: smartcontracts * contracts: delete dead code Co-authored-by: smartcontracts * contracts: update hh deploy scripts Co-authored-by: smartcontracts * contracts: update gas snapshot Co-authored-by: smartcontracts * integration-tests: update for new messengers Co-authored-by: smartcontracts * ops: devnet up script new genesis Co-authored-by: smartcontracts * opnode: new bindings Co-authored-by: smartcontracts * l2os: new bindings Co-authored-by: smartcontracts * contracts: update gas snapshot Co-authored-by: smartcontracts * specs: L2ToL1MessagePasser * specs: messengers * specs: bridges * specs: fix linting issues * contracts: better contract for is optimism mintable * contracts: fix some comments * contracts: assert finalization window has passed * bindings: regenerate * contracts: address comments Co-authored-by: Mark Tyneway * opnode: p2p RPC, fix static-peers * StateViz: Visualize rollup state changes (#419) * StateViz: Visualize rollup state changes * remove DEBUGMEs * Add L1WindowBuf to viz * move stateviz to opnode/cmd * Add stateviz to docker-compose * html lint * Add SRI * Fix linter * Merge fixes * Fixes from code review * Imports * Fix space Co-authored-by: Matthew Slipper * contracts: add backup logic for deposits Introduces backup logic for deposits that prevents users from accidentally making bad deposits. Deposits that don't complete successfully will be returned back to the layer where the deposits were made. * contracts: add base gas to sent messages Introduces base gas to the CrossDomainMessenger's sendMessage function. Base gas is used to guarantee that all messages sent between messengers will at the very least be able to store the message hash on the other chain and therefore be replayable. Base gas scales dynamically with the size of the message. * contracts: use clearer message encoding Uses the clearer abi.encodeWithSelector within CrossDomainMessenger's sendMessage function. Doing this makes it much more apparent that a call to the relayMessage function is being triggered. * contracts: add L1BlockNumber predeploy Re-introduces the L1BlockNumber predeployed contract for backwards compatibility with the previous OVM_L1BlockNumber contract. * opnode: p2p rpc client bindings, test update, minor p2p flag update * opnode/p2p: set up connection notification before starting host B * opnode/p2p: bundle p2p components into p2p node for separate testing * opnode/p2p: minor style fix + rename * opnode/p2p: add method to get peer info about self * Fix SRI on stateviz (#440) * Fix L2 Output Timestamps (#416) There was an off by one error in the L2 Output Oracle contract in the timestamp to block number conversion. In addition, the L2 Output Submitter (op proposer) did not recognize that there was a mismatch between the timestamp/block number in the header and the timestamp/block number from the contract. This bug causes problems when trying to do withdrawals. Fixes ENG-2128 * opnode: Withdrawal E2E test in go (#423) This does the following: - Adds withdrawal utilities (to opnode/withdrawals) - Adds an end to end test in go of withdrawals - Adds the L2 withdrawer contract - Updates to a newer version of reference-go-ethereum Fixes ENG-2202 * Adopt go.work, rename modules to prep for monorepo (#441) Co-Authored-By: Matthew Slipper Co-authored-by: protolambda * remove git submodules * optimistic-specs: monorepo merge, mv into protocol dir * bedrock contracts dependencies * monorepo merge: fix Go env * monorepo merge: move contracts-bedrock into non-Lerna package * monorepo merge: bring back CI Known issues: - There are broken links that `lychee` picks up on. These still need to be updated. - Slither returns errors, both here and in `optimistic-specs`. - `go-bip39` was updated to a newer version. The newer version broke a `bss-core` test, which had to be fixed. - Forge is not compatible with Lerna. As a result, the `contracts-bedrock` package had to be moved out of the `packages` hierarchy. - The devnet itests don't work because the Go modules aren't on the default branch. We need to decide if we merge to develop, or stay on a feature branch before fixing this. * outline bedrock dirs, rm stale protocol repo files (#2562) * monorepo merge: re-run yarn Co-authored-by: Joshua Gutow Co-authored-by: Diederik Loerakker Co-authored-by: Mark Tyneway Co-authored-by: Maurelian Co-authored-by: smartcontracts Co-authored-by: Murphy Law Co-authored-by: norswap Co-authored-by: Javed Khan Co-authored-by: Murphy Law Co-authored-by: Georgios Konstantopoulos Co-authored-by: Conner Fromknecht Co-authored-by: Luca Donno <30298476+lucadonnoh@users.noreply.github.com> Co-authored-by: Ben Wilson Co-authored-by: Ben Wilson <82120899+optimisticben@users.noreply.github.com> --- proxyd/proxyd/go.mod | 55 +++++++++++++++++++++++++---- proxyd/proxyd/go.sum | 82 +++++++++++++++++++------------------------- 2 files changed, 84 insertions(+), 53 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 9ed5978..8fe7035 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -1,22 +1,65 @@ module github.com/ethereum-optimism/optimism/proxyd -go 1.16 +go 1.18 require ( github.com/BurntSushi/toml v0.4.1 - github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/alicebob/miniredis v2.5.0+incompatible github.com/ethereum/go-ethereum v1.10.16 github.com/go-redis/redis/v8 v8.11.4 github.com/golang/snappy v0.0.4 - github.com/gomodule/redigo v1.8.8 // indirect github.com/gorilla/mux v1.8.0 - github.com/gorilla/websocket v1.4.2 + github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/prometheus/client_golang v1.11.0 - github.com/rs/cors v1.8.0 + github.com/rs/cors v1.8.2 github.com/stretchr/testify v1.7.0 - github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c +) + +require ( + github.com/VictoriaMetrics/fastcache v1.9.0 // indirect + github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btcd v0.22.0-beta // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/deckarep/golang-set v1.8.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/fjl/memsize v0.0.1 // indirect + github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/gomodule/redigo v1.8.8 // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/go-bexpr v0.1.11 // indirect + github.com/huin/goupnp v1.0.3 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.30.0 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/prometheus/tsdb v0.10.0 // indirect + github.com/rjeczalik/notify v0.9.2 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d // indirect + github.com/tklauser/go-sysconf v0.3.10 // indirect + github.com/tklauser/numcpus v0.4.0 // indirect + github.com/tyler-smith/go-bip39 v1.1.0 // indirect + github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect + golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect + golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 128f28c..7a2e81f 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -37,10 +37,9 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/VictoriaMetrics/fastcache v1.9.0 h1:oMwsS6c8abz98B7ytAewQ7M1ZN/Im/iwKoE1euaFvhs= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -70,8 +69,8 @@ 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/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -82,7 +81,6 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -116,26 +114,24 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.10.16 h1:3oPrumn0bCW/idjcxMn5YYVCdK7VzJYIvwGZUGLEaoc= github.com/ethereum/go-ethereum v1.10.16/go.mod h1:Anj6cxczl+AHy63o4X9O8yWNHuN5wMpfb8MAnHkWn7Y= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= @@ -147,18 +143,16 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -201,8 +195,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/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.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -210,18 +204,18 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= @@ -231,8 +225,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.2 h1:RfGLP+h3mvisuWEyybxNq5Eft3NWhHLPeUN72kpKZoI= github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= +github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -256,7 +250,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -289,7 +282,6 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -297,26 +289,26 @@ github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIG github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -372,28 +364,28 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= -github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -404,8 +396,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d h1:vmirMegf1vqPJ+lDBxLQ0MAt3tz+JL57UPxu44JBOjA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -418,14 +410,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= +github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= +github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= @@ -435,6 +425,7 @@ github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6Ut github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -451,8 +442,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -505,8 +496,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -566,8 +557,8 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -576,13 +567,13 @@ 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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -614,7 +605,6 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -660,8 +650,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -670,8 +660,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= From 7d775817259f1c1fb3abffeeb5aed18956e545d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 May 2022 23:35:13 +0000 Subject: [PATCH 004/212] chore(deps): bump github.com/ethereum/go-ethereum in /proxyd (#2607) Bumps [github.com/ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) from 1.10.16 to 1.10.17. - [Release notes](https://github.com/ethereum/go-ethereum/releases) - [Commits](https://github.com/ethereum/go-ethereum/compare/v1.10.16...v1.10.17) --- updated-dependencies: - dependency-name: github.com/ethereum/go-ethereum dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- proxyd/proxyd/go.mod | 5 +- proxyd/proxyd/go.sum | 229 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 213 insertions(+), 21 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 8fe7035..73d7d85 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/BurntSushi/toml v0.4.1 github.com/alicebob/miniredis v2.5.0+incompatible - github.com/ethereum/go-ethereum v1.10.16 + github.com/ethereum/go-ethereum v1.10.17 github.com/go-redis/redis/v8 v8.11.4 github.com/golang/snappy v0.0.4 github.com/gorilla/mux v1.8.0 @@ -22,9 +22,11 @@ require ( github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd v0.22.0-beta // indirect + github.com/btcsuite/btcd/btcec/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set v1.8.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/fjl/memsize v0.0.1 // indirect @@ -57,7 +59,6 @@ require ( github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect - golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 7a2e81f..d6d5053 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -8,29 +8,36 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= -github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -40,6 +47,7 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VictoriaMetrics/fastcache v1.9.0 h1:oMwsS6c8abz98B7ytAewQ7M1ZN/Im/iwKoE1euaFvhs= +github.com/VictoriaMetrics/fastcache v1.9.0/go.mod h1:otoTS3xu+6IzF/qByjqzjp3rTuzM3Qf0ScU1UTj97iU= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -71,11 +79,18 @@ github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+Wji github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= +github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btcd/btcec/v2 v2.1.2 h1:YoYoC9J0jwfukodSBMzZYUVQ8PTiYg4BnOWiJVzTmLs= +github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= @@ -90,6 +105,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P 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/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -102,6 +118,11 @@ 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 v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -110,25 +131,32 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= +github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.16 h1:3oPrumn0bCW/idjcxMn5YYVCdK7VzJYIvwGZUGLEaoc= -github.com/ethereum/go-ethereum v1.10.16/go.mod h1:Anj6cxczl+AHy63o4X9O8yWNHuN5wMpfb8MAnHkWn7Y= +github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= +github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= +github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= 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/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -137,6 +165,7 @@ github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1T github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -145,6 +174,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= @@ -153,32 +183,45 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= +github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -193,19 +236,28 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/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.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -213,9 +265,11 @@ 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.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= 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/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= @@ -225,8 +279,9 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= +github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= +github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -245,6 +300,7 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -291,28 +347,32 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= +github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -330,6 +390,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 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= @@ -366,26 +427,33 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= +github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= +github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -398,6 +466,7 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d h1:vmirMegf1vqPJ+lDBxLQ0MAt3tz+JL57UPxu44JBOjA= +github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d/go.mod h1:97vT0Rym0wCnK4B++hNA3nCetr0Mh1KXaVxzSt1arjg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -412,23 +481,32 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45 github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= +github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= 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.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -439,11 +517,14 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -454,6 +535,9 @@ golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm0 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -465,13 +549,18 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -485,17 +574,32 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -503,6 +607,7 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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= @@ -510,6 +615,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ 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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= @@ -517,6 +623,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -532,6 +639,8 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -539,12 +648,23 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -557,10 +677,16 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -569,11 +695,14 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= +golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -597,9 +726,28 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -619,11 +767,21 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -638,20 +796,49 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +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.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -685,6 +872,10 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From c43d2c708d39a8c28bb67dbe1f05ae249a714112 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 1 Jun 2022 21:25:41 +0000 Subject: [PATCH 005/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 6 ++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 4623bb0..fcd9250 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,11 @@ # @eth-optimism/proxyd +## 3.8.7 + +### Patch Changes + +- 6f458607: Bump go-ethereum to 1.10.17 + ## 3.8.6 ### Patch Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 3087ed4..383abc0 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.8.6", + "version": "3.8.7", "private": true, "dependencies": {} } From 69f189c0ea3c5cf30afd793bf2dbfd5489cba6cb Mon Sep 17 00:00:00 2001 From: Murphy Law Date: Wed, 8 Jun 2022 09:56:24 -0400 Subject: [PATCH 006/212] proxyd: Handle unexpected JSON-RPC responses (#2628) This fixes a bug where the infura backend would be labeled offline because it returns an unexpected JSON-RPC response. Unexpected, but well-formed, JSON-RPC response are handled specially. Such errors are surfaced up to the backend proxier so failover still occurs. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- proxyd/proxyd/backend.go | 31 +++++++++-- .../proxyd/integration_tests/failover_test.go | 51 +++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 2e974af..cc06137 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -73,6 +73,8 @@ var ( Message: "gateway timeout", HTTPErrorCode: 504, } + + ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") ) func ErrInvalidRequest(msg string) *RPCErr { @@ -228,7 +230,20 @@ func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([] ) res, err := b.doForward(ctx, reqs, isBatch) - if err != nil { + switch err { + case nil: // do nothing + // ErrBackendUnexpectedJSONRPC occurs because infura responds with a single JSON-RPC object + // to a batch request whenever any Request Object in the batch would induce a partial error. + // We don't label the the backend offline in this case. But the error is still returned to + // callers so failover can occur if needed. + case ErrBackendUnexpectedJSONRPC: + log.Debug( + "Reecived unexpected JSON-RPC response", + "name", b.Name, + "req_id", GetReqID(ctx), + "err", err, + ) + default: lastError = err log.Warn( "backend request failed, trying again", @@ -244,7 +259,7 @@ func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([] timer.ObserveDuration() MaybeRecordErrorsInRPCRes(ctx, b.Name, reqs, res) - return res, nil + return res, err } b.setOffline() @@ -387,12 +402,15 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool var res []*RPCRes if err := json.Unmarshal(resB, &res); err != nil { + // Infura may return a single JSON-RPC response if, for example, the batch contains a request for an unsupported method + if responseIsNotBatched(resB) { + return nil, ErrBackendUnexpectedJSONRPC + } return nil, ErrBackendBadResponse } - // Alas! Certain node providers (Infura) always return a single JSON object for some types of errors if len(rpcReqs) != len(res) { - return nil, ErrBackendBadResponse + return nil, ErrBackendUnexpectedJSONRPC } // capture the HTTP status code in the response. this will only @@ -407,6 +425,11 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool return res, nil } +func responseIsNotBatched(b []byte) bool { + var r RPCRes + return json.Unmarshal(b, &r) == nil +} + // sortBatchRPCResponse sorts the RPCRes slice according to the position of its corresponding ID in the RPCReq slice func sortBatchRPCResponse(req []*RPCReq, res []*RPCRes) { pos := make(map[string]int, len(req)) diff --git a/proxyd/proxyd/integration_tests/failover_test.go b/proxyd/proxyd/integration_tests/failover_test.go index f99d1e6..f80f47c 100644 --- a/proxyd/proxyd/integration_tests/failover_test.go +++ b/proxyd/proxyd/integration_tests/failover_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/alicebob/miniredis" "github.com/ethereum-optimism/optimism/proxyd" "github.com/stretchr/testify/require" ) @@ -15,6 +16,7 @@ import ( const ( goodResponse = `{"jsonrpc": "2.0", "result": "hello", "id": 999}` noBackendsResponse = `{"error":{"code":-32011,"message":"no backends available for method"},"id":999,"jsonrpc":"2.0"}` + unexpectedResponse = `{"error":{"code":-32011,"message":"some error"},"id":999,"jsonrpc":"2.0"}` ) func TestFailover(t *testing.T) { @@ -240,3 +242,52 @@ func TestBatchWithPartialFailover(t *testing.T) { require.Equal(t, 2, len(badBackend.Requests())) require.Equal(t, 2, len(goodBackend.Requests())) } + +func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { + InitLogger() + // Scenario: + // 1. Send batch to BAD_BACKEND (Infura) + // 2. Infura fails completely due to a partially errorneous batch request (one of N+1 request object is invalid) + // 3. Assert that the request batch is re-routed to the failover provider + // 4. Assert that BAD_BACKEND is NOT labeled offline + // 5. Assert that BAD_BACKEND is NOT retried + + redis, err := miniredis.Run() + require.NoError(t, err) + defer redis.Close() + + config := ReadConfig("failover") + config.Server.MaxUpstreamBatchSize = 2 + config.BackendOptions.MaxRetries = 2 + // Setup redis to detect offline backends + config.Redis.URL = fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()) + + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse, goodResponse)) + defer goodBackend.Close() + badBackend := NewMockBackend(SingleResponseHandler(200, unexpectedResponse)) + defer badBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) + + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + res, statusCode, err := client.SendBatchRPC( + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("2", "eth_chainId", nil), + ) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + RequireEqualJSON(t, []byte(asArray(goodResponse, goodResponse)), res) + require.Equal(t, 1, len(badBackend.Requests())) + require.Equal(t, 1, len(goodBackend.Requests())) + + rr, err := proxyd.NewRedisRateLimiter(config.Redis.URL) + require.NoError(t, err) + online, err := rr.IsBackendOnline("bad") + require.NoError(t, err) + require.Equal(t, true, online) +} From e41cfc1d945654a63fab26da9f16e44b22248c75 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 8 Jun 2022 09:09:32 -0600 Subject: [PATCH 007/212] proxyd: Fix concurrent WS write panic (#2711) Fixes a panic in the websocket proxyd logic. Normally, the `clientPump` and `backendPump` methods in `WSProxier` send data in one direction. However, when the client sends an invalid RPC, the `clientPump` will send a response _directly to the client_ in order to avoid unnecessary roundtrips to the backend. This could be interleaved with concurrent writes to the client's WS in `backendPump`, and would cause a panic in the WS library. To test this, this PR includes a dedicated integration test that reliably triggers the issue. In addition, this PR adds additional testing for WS functionality. --- proxyd/proxyd/backend.go | 81 +++-- .../integration_tests/mock_backend_test.go | 71 +++++ .../proxyd/integration_tests/testdata/ws.toml | 25 ++ proxyd/proxyd/integration_tests/util_test.go | 82 ++++- proxyd/proxyd/integration_tests/ws_test.go | 281 ++++++++++++++++++ proxyd/proxyd/server.go | 8 + 6 files changed, 509 insertions(+), 39 deletions(-) create mode 100644 proxyd/proxyd/integration_tests/testdata/ws.toml create mode 100644 proxyd/proxyd/integration_tests/ws_test.go diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index cc06137..94e94d1 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -15,6 +15,7 @@ import ( "sort" "strconv" "strings" + "sync" "time" "github.com/ethereum/go-ethereum/log" @@ -548,6 +549,7 @@ type WSProxier struct { clientConn *websocket.Conn backendConn *websocket.Conn methodWhitelist *StringSet + clientConnMu sync.Mutex } func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, methodWhitelist *StringSet) *WSProxier { @@ -570,12 +572,11 @@ func (w *WSProxier) Proxy(ctx context.Context) error { func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { for { - outConn := w.backendConn // Block until we get a message. msgType, msg, err := w.clientConn.ReadMessage() if err != nil { errC <- err - if err := outConn.WriteMessage(websocket.CloseMessage, formatWSError(err)); err != nil { + if err := w.backendConn.WriteMessage(websocket.CloseMessage, formatWSError(err)); err != nil { log.Error("error writing backendConn message", "err", err) } return @@ -586,7 +587,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { // Route control messages to the backend. These don't // count towards the total RPC requests count. if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage { - err := outConn.WriteMessage(msgType, msg) + err := w.backendConn.WriteMessage(msgType, msg) if err != nil { errC <- err return @@ -612,20 +613,27 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { "req_id", GetReqID(ctx), "err", err, ) - outConn = w.clientConn msg = mustMarshalJSON(NewRPCErrorRes(id, err)) RecordRPCError(ctx, BackendProxyd, method, err) - } else { - RecordRPCForward(ctx, w.backend.Name, req.Method, RPCRequestSourceWS) - log.Info( - "forwarded WS message to backend", - "method", req.Method, - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) + + // Send error response to client + err = w.writeClientConn(msgType, msg) + if err != nil { + errC <- err + return + } + continue } - err = outConn.WriteMessage(msgType, msg) + RecordRPCForward(ctx, w.backend.Name, req.Method, RPCRequestSourceWS) + log.Info( + "forwarded WS message to backend", + "method", req.Method, + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + + err = w.backendConn.WriteMessage(msgType, msg) if err != nil { errC <- err return @@ -639,7 +647,7 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { msgType, msg, err := w.backendConn.ReadMessage() if err != nil { errC <- err - if err := w.clientConn.WriteMessage(websocket.CloseMessage, formatWSError(err)); err != nil { + if err := w.writeClientConn(websocket.CloseMessage, formatWSError(err)); err != nil { log.Error("error writing clientConn message", "err", err) } return @@ -649,7 +657,7 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { // Route control messages directly to the client. if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage { - err := w.clientConn.WriteMessage(msgType, msg) + err := w.writeClientConn(msgType, msg) if err != nil { errC <- err return @@ -664,26 +672,28 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { id = res.ID } msg = mustMarshalJSON(NewRPCErrorRes(id, err)) - } - if res.IsError() { - log.Info( - "backend responded with RPC error", - "code", res.Error.Code, - "msg", res.Error.Message, - "source", "ws", - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - RecordRPCError(ctx, w.backend.Name, MethodUnknown, res.Error) + log.Info("backend responded with error", "err", err) } else { - log.Info( - "forwarded WS message to client", - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) + if res.IsError() { + log.Info( + "backend responded with RPC error", + "code", res.Error.Code, + "msg", res.Error.Message, + "source", "ws", + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + RecordRPCError(ctx, w.backend.Name, MethodUnknown, res.Error) + } else { + log.Info( + "forwarded WS message to client", + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + } } - err = w.clientConn.WriteMessage(msgType, msg) + err = w.writeClientConn(msgType, msg) if err != nil { errC <- err return @@ -726,6 +736,13 @@ func (w *WSProxier) parseBackendMsg(msg []byte) (*RPCRes, error) { return res, nil } +func (w *WSProxier) writeClientConn(msgType int, msg []byte) error { + w.clientConnMu.Lock() + err := w.clientConn.WriteMessage(msgType, msg) + w.clientConnMu.Unlock() + return err +} + func mustMarshalJSON(in interface{}) []byte { out, err := json.Marshal(in) if err != nil { diff --git a/proxyd/proxyd/integration_tests/mock_backend_test.go b/proxyd/proxyd/integration_tests/mock_backend_test.go index 94b00e1..7f40ffe 100644 --- a/proxyd/proxyd/integration_tests/mock_backend_test.go +++ b/proxyd/proxyd/integration_tests/mock_backend_test.go @@ -7,9 +7,11 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "strings" "sync" "github.com/ethereum-optimism/optimism/proxyd" + "github.com/gorilla/websocket" ) type RecordedRequest struct { @@ -251,3 +253,72 @@ func (m *MockBackend) wrappedHandler(w http.ResponseWriter, r *http.Request) { m.handler.ServeHTTP(w, clone) m.mtx.Unlock() } + +type MockWSBackend struct { + connCB MockWSBackendOnConnect + msgCB MockWSBackendOnMessage + closeCB MockWSBackendOnClose + server *httptest.Server + upgrader websocket.Upgrader + conns []*websocket.Conn + connsMu sync.Mutex +} + +type MockWSBackendOnConnect func(conn *websocket.Conn) +type MockWSBackendOnMessage func(conn *websocket.Conn, msgType int, data []byte) +type MockWSBackendOnClose func(conn *websocket.Conn, err error) + +func NewMockWSBackend( + connCB MockWSBackendOnConnect, + msgCB MockWSBackendOnMessage, + closeCB MockWSBackendOnClose, +) *MockWSBackend { + mb := &MockWSBackend{ + connCB: connCB, + msgCB: msgCB, + closeCB: closeCB, + } + mb.server = httptest.NewServer(mb) + return mb +} + +func (m *MockWSBackend) ServeHTTP(w http.ResponseWriter, r *http.Request) { + conn, err := m.upgrader.Upgrade(w, r, nil) + if err != nil { + panic(err) + } + if m.connCB != nil { + m.connCB(conn) + } + go func() { + for { + mType, msg, err := conn.ReadMessage() + if err != nil { + if m.closeCB != nil { + m.closeCB(conn, err) + } + return + } + if m.msgCB != nil { + m.msgCB(conn, mType, msg) + } + } + }() + m.connsMu.Lock() + m.conns = append(m.conns, conn) + m.connsMu.Unlock() +} + +func (m *MockWSBackend) URL() string { + return strings.Replace(m.server.URL, "http://", "ws://", 1) +} + +func (m *MockWSBackend) Close() { + m.server.Close() + + m.connsMu.Lock() + for _, conn := range m.conns { + conn.Close() + } + m.connsMu.Unlock() +} diff --git a/proxyd/proxyd/integration_tests/testdata/ws.toml b/proxyd/proxyd/integration_tests/testdata/ws.toml new file mode 100644 index 0000000..27ecb23 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/ws.toml @@ -0,0 +1,25 @@ +ws_backend_group = "main" + +ws_method_whitelist = [ + "eth_subscribe" +] + +[server] +rpc_port = 8545 +ws_port = 8546 + +[backend] +response_timeout_seconds = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" +max_ws_conns = 1 + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" diff --git a/proxyd/proxyd/integration_tests/util_test.go b/proxyd/proxyd/integration_tests/util_test.go index bd798ec..c5c15a6 100644 --- a/proxyd/proxyd/integration_tests/util_test.go +++ b/proxyd/proxyd/integration_tests/util_test.go @@ -8,22 +8,26 @@ import ( "net/http" "os" "testing" + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/gorilla/websocket" "github.com/BurntSushi/toml" "github.com/ethereum-optimism/optimism/proxyd" - "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) -type ProxydClient struct { +type ProxydHTTPClient struct { url string } -func NewProxydClient(url string) *ProxydClient { - return &ProxydClient{url: url} +func NewProxydClient(url string) *ProxydHTTPClient { + return &ProxydHTTPClient{url: url} } -func (p *ProxydClient) SendRPC(method string, params []interface{}) ([]byte, int, error) { +func (p *ProxydHTTPClient) SendRPC(method string, params []interface{}) ([]byte, int, error) { rpcReq := NewRPCReq("999", method, params) body, err := json.Marshal(rpcReq) if err != nil { @@ -32,7 +36,7 @@ func (p *ProxydClient) SendRPC(method string, params []interface{}) ([]byte, int return p.SendRequest(body) } -func (p *ProxydClient) SendBatchRPC(reqs ...*proxyd.RPCReq) ([]byte, int, error) { +func (p *ProxydHTTPClient) SendBatchRPC(reqs ...*proxyd.RPCReq) ([]byte, int, error) { body, err := json.Marshal(reqs) if err != nil { panic(err) @@ -40,7 +44,7 @@ func (p *ProxydClient) SendBatchRPC(reqs ...*proxyd.RPCReq) ([]byte, int, error) return p.SendRequest(body) } -func (p *ProxydClient) SendRequest(body []byte) ([]byte, int, error) { +func (p *ProxydHTTPClient) SendRequest(body []byte) ([]byte, int, error) { res, err := http.Post(p.url, "application/json", bytes.NewReader(body)) if err != nil { return nil, -1, err @@ -98,6 +102,70 @@ func NewRPCReq(id string, method string, params []interface{}) *proxyd.RPCReq { } } +type ProxydWSClient struct { + conn *websocket.Conn + msgCB ProxydWSClientOnMessage + closeCB ProxydWSClientOnClose +} + +type WSMessage struct { + Type int + Body []byte +} + +type ProxydWSClientOnMessage func(msgType int, data []byte) +type ProxydWSClientOnClose func(err error) + +func NewProxydWSClient( + url string, + msgCB ProxydWSClientOnMessage, + closeCB ProxydWSClientOnClose, +) (*ProxydWSClient, error) { + conn, _, err := websocket.DefaultDialer.Dial(url, nil) // nolint:bodyclose + if err != nil { + return nil, err + } + + c := &ProxydWSClient{ + conn: conn, + msgCB: msgCB, + closeCB: closeCB, + } + go c.readPump() + return c, nil +} + +func (h *ProxydWSClient) readPump() { + for { + mType, msg, err := h.conn.ReadMessage() + if err != nil { + if h.closeCB != nil { + h.closeCB(err) + } + return + } + if h.msgCB != nil { + h.msgCB(mType, msg) + } + } +} + +func (h *ProxydWSClient) HardClose() { + h.conn.Close() +} + +func (h *ProxydWSClient) SoftClose() error { + return h.WriteMessage(websocket.CloseMessage, nil) +} + +func (h *ProxydWSClient) WriteMessage(msgType int, msg []byte) error { + return h.conn.WriteMessage(msgType, msg) +} + +func (h *ProxydWSClient) WriteControlMessage(msgType int, msg []byte) error { + return h.conn.WriteControl(msgType, msg, time.Now().Add(time.Minute)) +} + func InitLogger() { log.Root().SetHandler( log.LvlFilterHandler(log.LvlDebug, diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go new file mode 100644 index 0000000..563b689 --- /dev/null +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -0,0 +1,281 @@ +package integration_tests + +import ( + "os" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/gorilla/websocket" + "github.com/stretchr/testify/require" +) + +// TestConcurrentWSPanic tests for a panic in the websocket proxy +// that occurred when messages were sent from the upstream to the +// client right after the client sent an invalid request. +func TestConcurrentWSPanic(t *testing.T) { + var backendToProxyConn *websocket.Conn + var setOnce sync.Once + + readyCh := make(chan struct{}, 1) + quitC := make(chan struct{}) + + // Pull out the backend -> proxyd conn so that we can spam it directly. + // Use a sync.Once to make sure we only do that once, for the first + // connection. + backend := NewMockWSBackend(func(conn *websocket.Conn) { + setOnce.Do(func() { + backendToProxyConn = conn + readyCh <- struct{}{} + }) + }, nil, nil) + defer backend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) + + config := ReadConfig("ws") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + client, err := NewProxydWSClient("ws://127.0.0.1:8546", nil, nil) + require.NoError(t, err) + defer shutdown() + + <-readyCh + + // spam messages + go func() { + for { + select { + case <-quitC: + return + default: + _ = backendToProxyConn.WriteMessage(websocket.TextMessage, []byte("garbage")) + } + } + }() + + // spam invalid RPCs + go func() { + for { + select { + case <-quitC: + return + default: + _ = client.WriteMessage(websocket.TextMessage, []byte("{\"id\": 1, \"method\": \"eth_foo\", \"params\": [\"newHeads\"]}")) + } + } + }() + + // 1 second is enough to trigger the panic due to + // concurrent write to websocket connection + time.Sleep(time.Second) + close(quitC) +} + +type backendHandler struct { + msgCB atomic.Value + closeCB atomic.Value +} + +func (b *backendHandler) MsgCB(conn *websocket.Conn, msgType int, data []byte) { + cb := b.msgCB.Load() + if cb == nil { + return + } + cb.(MockWSBackendOnMessage)(conn, msgType, data) +} + +func (b *backendHandler) SetMsgCB(cb MockWSBackendOnMessage) { + b.msgCB.Store(cb) +} + +func (b *backendHandler) CloseCB(conn *websocket.Conn, err error) { + cb := b.closeCB.Load() + if cb == nil { + return + } + cb.(MockWSBackendOnClose)(conn, err) +} + +func (b *backendHandler) SetCloseCB(cb MockWSBackendOnClose) { + b.closeCB.Store(cb) +} + +type clientHandler struct { + msgCB atomic.Value +} + +func (c *clientHandler) MsgCB(msgType int, data []byte) { + cb := c.msgCB.Load().(ProxydWSClientOnMessage) + if cb == nil { + return + } + cb(msgType, data) +} + +func (c *clientHandler) SetMsgCB(cb ProxydWSClientOnMessage) { + c.msgCB.Store(cb) +} + +func TestWS(t *testing.T) { + backendHdlr := new(backendHandler) + clientHdlr := new(clientHandler) + + backend := NewMockWSBackend(nil, func(conn *websocket.Conn, msgType int, data []byte) { + backendHdlr.MsgCB(conn, msgType, data) + }, func(conn *websocket.Conn, err error) { + backendHdlr.CloseCB(conn, err) + }) + defer backend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) + + config := ReadConfig("ws") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { + clientHdlr.MsgCB(msgType, data) + }, nil) + defer client.HardClose() + require.NoError(t, err) + defer shutdown() + + tests := []struct { + name string + backendRes string + expRes string + clientReq string + }{ + { + "ok response", + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0xcd0c3e8af590364c09d0fa6a1210faf5\"}", + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0xcd0c3e8af590364c09d0fa6a1210faf5\"}", + "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"newHeads\"]}", + }, + { + "garbage backend response", + "gibblegabble", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32013,\"message\":\"backend returned an invalid response\"},\"id\":null}", + "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"newHeads\"]}", + }, + { + "blacklisted RPC", + "}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"rpc method is not whitelisted\"},\"id\":1}", + "{\"id\": 1, \"method\": \"eth_whatever\", \"params\": []}", + }, + { + "garbage client request", + "{}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"parse error\"},\"id\":null}", + "barf", + }, + { + "invalid client request", + "{}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"parse error\"},\"id\":null}", + "{\"jsonrpc\": \"2.0\", \"method\": true}", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + timeout := time.NewTicker(30 * time.Second) + doneCh := make(chan struct{}, 1) + backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) { + require.NoError(t, conn.WriteMessage(websocket.TextMessage, []byte(tt.backendRes))) + }) + clientHdlr.SetMsgCB(func(msgType int, data []byte) { + require.Equal(t, tt.expRes, string(data)) + doneCh <- struct{}{} + }) + require.NoError(t, client.WriteMessage( + websocket.TextMessage, + []byte(tt.clientReq), + )) + select { + case <-timeout.C: + t.Fatalf("timed out") + case <-doneCh: + return + } + }) + } +} + +func TestWSClientClosure(t *testing.T) { + backendHdlr := new(backendHandler) + clientHdlr := new(clientHandler) + + backend := NewMockWSBackend(nil, func(conn *websocket.Conn, msgType int, data []byte) { + backendHdlr.MsgCB(conn, msgType, data) + }, func(conn *websocket.Conn, err error) { + backendHdlr.CloseCB(conn, err) + }) + defer backend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) + + config := ReadConfig("ws") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + for _, closeType := range []string{"soft", "hard"} { + t.Run(closeType, func(t *testing.T) { + client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { + clientHdlr.MsgCB(msgType, data) + }, nil) + require.NoError(t, err) + + timeout := time.NewTicker(30 * time.Second) + doneCh := make(chan struct{}, 1) + backendHdlr.SetCloseCB(func(conn *websocket.Conn, err error) { + doneCh <- struct{}{} + }) + + if closeType == "soft" { + require.NoError(t, client.SoftClose()) + } else { + client.HardClose() + } + + select { + case <-timeout.C: + t.Fatalf("timed out") + case <-doneCh: + return + } + }) + } +} + +func TestWSClientMaxConns(t *testing.T) { + backend := NewMockWSBackend(nil, nil, nil) + defer backend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) + + config := ReadConfig("ws") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + doneCh := make(chan struct{}, 1) + _, err = NewProxydWSClient("ws://127.0.0.1:8546", nil, nil) + require.NoError(t, err) + _, err = NewProxydWSClient("ws://127.0.0.1:8546", nil, func(err error) { + require.Contains(t, err.Error(), "unexpected EOF") + doneCh <- struct{}{} + }) + require.NoError(t, err) + + timeout := time.NewTicker(30 * time.Second) + select { + case <-timeout.C: + t.Fatalf("timed out") + case <-doneCh: + return + } +} diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 30559d9..f34da71 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -11,6 +11,7 @@ import ( "net/http" "strconv" "strings" + "sync" "time" "github.com/ethereum/go-ethereum/log" @@ -44,6 +45,7 @@ type Server struct { rpcServer *http.Server wsServer *http.Server cache RPCCache + srvMu sync.Mutex } func NewServer( @@ -90,6 +92,7 @@ func NewServer( } func (s *Server) RPCListenAndServe(host string, port int) error { + s.srvMu.Lock() hdlr := mux.NewRouter() hdlr.HandleFunc("/healthz", s.HandleHealthz).Methods("GET") hdlr.HandleFunc("/", s.HandleRPC).Methods("POST") @@ -103,10 +106,12 @@ func (s *Server) RPCListenAndServe(host string, port int) error { Addr: addr, } log.Info("starting HTTP server", "addr", addr) + s.srvMu.Unlock() return s.rpcServer.ListenAndServe() } func (s *Server) WSListenAndServe(host string, port int) error { + s.srvMu.Lock() hdlr := mux.NewRouter() hdlr.HandleFunc("/", s.HandleWS) hdlr.HandleFunc("/{authorization}", s.HandleWS) @@ -119,10 +124,13 @@ func (s *Server) WSListenAndServe(host string, port int) error { Addr: addr, } log.Info("starting WS server", "addr", addr) + s.srvMu.Unlock() return s.wsServer.ListenAndServe() } func (s *Server) Shutdown() { + s.srvMu.Lock() + defer s.srvMu.Unlock() if s.rpcServer != nil { _ = s.rpcServer.Shutdown(context.Background()) } From c31d7501ea8ceca8bdadf3218fc13a06415a25e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Jun 2022 20:51:10 +0000 Subject: [PATCH 008/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 7 +++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index fcd9250..934da4f 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,12 @@ # @eth-optimism/proxyd +## 3.8.8 + +### Patch Changes + +- 58dc7adc: Improve robustness against unexpected JSON-RPC from upstream +- 552cd641: Fix concurrent write panic in WS + ## 3.8.7 ### Patch Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 383abc0..4f28b91 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.8.7", + "version": "3.8.8", "private": true, "dependencies": {} } From e2e3a622ce40857ae51bcfbef39ce7ee3270acfa Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Thu, 16 Jun 2022 13:02:39 -0600 Subject: [PATCH 009/212] proxyd: Use canned response for eth_accounts (#2801) We never want to expose Geth's accounts to the public internet, so proxyd will now return `[]` for `eth_accounts` RPC calls without hitting the backend. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- proxyd/proxyd/backend.go | 12 ++++++++++++ proxyd/proxyd/integration_tests/batching_test.go | 15 +++++++++++++++ proxyd/proxyd/integration_tests/testdata/ws.toml | 3 ++- proxyd/proxyd/integration_tests/ws_test.go | 6 ++++++ proxyd/proxyd/rpc.go | 8 ++++++++ proxyd/proxyd/server.go | 8 ++++++++ 6 files changed, 51 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 94e94d1..46e5225 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -625,6 +625,18 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { continue } + // Send eth_accounts requests directly to the client + if req.Method == "eth_accounts" { + msg = mustMarshalJSON(NewRPCRes(req.ID, emptyArrayResponse)) + RecordRPCForward(ctx, BackendProxyd, "eth_accounts", RPCRequestSourceWS) + err = w.writeClientConn(msgType, msg) + if err != nil { + errC <- err + return + } + continue + } + RecordRPCForward(ctx, w.backend.Name, req.Method, RPCRequestSourceWS) log.Info( "forwarded WS message to backend", diff --git a/proxyd/proxyd/integration_tests/batching_test.go b/proxyd/proxyd/integration_tests/batching_test.go index 59c6eb5..1bcbba9 100644 --- a/proxyd/proxyd/integration_tests/batching_test.go +++ b/proxyd/proxyd/integration_tests/batching_test.go @@ -18,6 +18,8 @@ func TestBatching(t *testing.T) { netVersionResponse1 := `{"jsonrpc": "2.0", "result": "1.0", "id": 1}` callResponse1 := `{"jsonrpc": "2.0", "result": "ekans1", "id": 1}` + ethAccountsResponse2 := `{"jsonrpc": "2.0", "result": [], "id": 2}` + type mockResult struct { method string id string @@ -98,6 +100,19 @@ func TestBatching(t *testing.T) { maxBatchSize: 2, numExpectedForwards: 3, }, + { + name: "eth_accounts does not get forwarded", + mocks: []mockResult{ + callMock1, + }, + reqs: []*proxyd.RPCReq{ + NewRPCReq("1", "eth_call", nil), + NewRPCReq("2", "eth_accounts", nil), + }, + expectedRes: asArray(callResponse1, ethAccountsResponse2), + maxBatchSize: 2, + numExpectedForwards: 1, + }, } for _, tt := range tests { diff --git a/proxyd/proxyd/integration_tests/testdata/ws.toml b/proxyd/proxyd/integration_tests/testdata/ws.toml index 27ecb23..f86b22f 100644 --- a/proxyd/proxyd/integration_tests/testdata/ws.toml +++ b/proxyd/proxyd/integration_tests/testdata/ws.toml @@ -1,7 +1,8 @@ ws_backend_group = "main" ws_method_whitelist = [ - "eth_subscribe" + "eth_subscribe", + "eth_accounts" ] [server] diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go index 563b689..6a63f71 100644 --- a/proxyd/proxyd/integration_tests/ws_test.go +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -178,6 +178,12 @@ func TestWS(t *testing.T) { "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"parse error\"},\"id\":null}", "{\"jsonrpc\": \"2.0\", \"method\": true}", }, + { + "eth_accounts", + "{}", + "{\"jsonrpc\":\"2.0\",\"result\":[],\"id\":1}", + "{\"jsonrpc\": \"2.0\", \"method\": \"eth_accounts\", \"id\": 1}", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/proxyd/proxyd/rpc.go b/proxyd/proxyd/rpc.go index 7809132..5f16822 100644 --- a/proxyd/proxyd/rpc.go +++ b/proxyd/proxyd/rpc.go @@ -142,6 +142,14 @@ func NewRPCErrorRes(id json.RawMessage, err error) *RPCRes { } } +func NewRPCRes(id json.RawMessage, result interface{}) *RPCRes { + return &RPCRes{ + JSONRPC: JSONRPCVersion, + Result: result, + ID: id, + } +} + func IsBatch(raw []byte) bool { for _, c := range raw { // skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index f34da71..f0db531 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -32,6 +32,8 @@ const ( defaultMaxUpstreamBatchSize = 10 ) +var emptyArrayResponse = json.RawMessage("[]") + type Server struct { backendGroups map[string]*BackendGroup wsBackendGroup *BackendGroup @@ -248,6 +250,12 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isB continue } + if parsedReq.Method == "eth_accounts" { + RecordRPCForward(ctx, BackendProxyd, "eth_accounts", RPCRequestSourceHTTP) + responses[i] = NewRPCRes(parsedReq.ID, emptyArrayResponse) + continue + } + group := s.rpcMethodMappings[parsedReq.Method] if group == "" { // use unknown below to prevent DOS vector that fills up memory From 5da617a5ba78eda16c5c5ca18d0c132861090f69 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 18 Jun 2022 02:48:41 +0000 Subject: [PATCH 010/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 6 ++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 934da4f..1c5e6a2 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,11 @@ # @eth-optimism/proxyd +## 3.8.9 + +### Patch Changes + +- 063c55cf: Use canned response for eth_accounts + ## 3.8.8 ### Patch Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 4f28b91..8192dc8 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.8.8", + "version": "3.8.9", "private": true, "dependencies": {} } From 614366aa5b501e15fd720443ab0a0b9c7ea90d88 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 22 Jun 2022 22:45:26 -0600 Subject: [PATCH 011/212] ci: Upgrade golangci-lint version to one that handles Go 1.18 (#2841) * ci: Upgrade golangci-lint version to one that handles Go 1.18 * Fix proxyd lint --- proxyd/proxyd/integration_tests/mock_backend_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/proxyd/proxyd/integration_tests/mock_backend_test.go b/proxyd/proxyd/integration_tests/mock_backend_test.go index 7f40ffe..b84b7d7 100644 --- a/proxyd/proxyd/integration_tests/mock_backend_test.go +++ b/proxyd/proxyd/integration_tests/mock_backend_test.go @@ -231,9 +231,7 @@ func (m *MockBackend) Requests() []*RecordedRequest { m.mtx.RLock() defer m.mtx.RUnlock() out := make([]*RecordedRequest, len(m.requests)) - for i := 0; i < len(m.requests); i++ { - out[i] = m.requests[i] - } + copy(out, m.requests) return out } From cd0afa3176e92aecd34ba35fada308a87af68943 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 27 Jul 2022 11:12:47 -0600 Subject: [PATCH 012/212] proxyd: Parameterize full RPC request logging (#3110) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- proxyd/proxyd/config.go | 3 +++ proxyd/proxyd/proxyd.go | 2 ++ proxyd/proxyd/server.go | 30 +++++++++++++++++++++--------- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 26929d9..db46167 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -18,6 +18,9 @@ type ServerConfig struct { TimeoutSeconds int `toml:"timeout_seconds"` MaxUpstreamBatchSize int `toml:"max_upstream_batch_size"` + + EnableRequestLog bool `toml:"enable_request_log"` + MaxRequestBodyLogLen int `toml:"max_request_body_log_len"` } type CacheConfig struct { diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index a730a60..4b616c5 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -222,6 +222,8 @@ func Start(config *Config) (func(), error) { secondsToDuration(config.Server.TimeoutSeconds), config.Server.MaxUpstreamBatchSize, rpcCache, + config.Server.EnableRequestLog, + config.Server.MaxRequestBodyLogLen, ) if config.Metrics.Enabled { diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index f0db531..58cd739 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -28,7 +28,7 @@ const ( MaxBatchRPCCalls = 100 cacheStatusHdr = "X-Proxyd-Cache-Status" defaultServerTimeout = time.Second * 10 - maxLogLength = 2000 + maxRequestBodyLogLen = 2000 defaultMaxUpstreamBatchSize = 10 ) @@ -40,6 +40,8 @@ type Server struct { wsMethodWhitelist *StringSet rpcMethodMappings map[string]string maxBodySize int64 + enableRequestLog bool + maxRequestBodyLogLen int authenticatedPaths map[string]string timeout time.Duration maxUpstreamBatchSize int @@ -60,6 +62,8 @@ func NewServer( timeout time.Duration, maxUpstreamBatchSize int, cache RPCCache, + enableRequestLog bool, + maxRequestBodyLogLen int, ) *Server { if cache == nil { cache = &NoopRPCCache{} @@ -87,6 +91,8 @@ func NewServer( timeout: timeout, maxUpstreamBatchSize: maxUpstreamBatchSize, cache: cache, + enableRequestLog: enableRequestLog, + maxRequestBodyLogLen: maxRequestBodyLogLen, upgrader: &websocket.Upgrader{ HandshakeTimeout: 5 * time.Second, }, @@ -169,11 +175,13 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { } RecordRequestPayloadSize(ctx, len(body)) - log.Info("Raw RPC request", - "body", truncate(string(body)), - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - ) + if s.enableRequestLog { + log.Info("Raw RPC request", + "body", truncate(string(body), s.maxRequestBodyLogLen), + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + ) + } if IsBatch(body) { reqs, err := ParseBatchRPCReq(body) @@ -527,9 +535,13 @@ func (n *NoopRPCCache) PutRPC(context.Context, *RPCReq, *RPCRes) error { return nil } -func truncate(str string) string { - if len(str) > maxLogLength { - return str[:maxLogLength] + "..." +func truncate(str string, maxLen int) string { + if maxLen == 0 { + maxLen = maxRequestBodyLogLen + } + + if len(str) > maxLen { + return str[:maxLen] + "..." } else { return str } From 4ea6a054c374a3c59d7624fb74d1ef0e72c3642c Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 3 Aug 2022 19:36:07 -0600 Subject: [PATCH 013/212] proxyd: Unwrap single RPC batches (#3165) * proxyd: Unwrap single RPC batches * Update backend.go --- proxyd/proxyd/backend.go | 32 +++++++++++++++---- .../integration_tests/mock_backend_test.go | 6 +++- proxyd/proxyd/integration_tests/ws_test.go | 5 +++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 46e5225..f4d7d93 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -349,7 +349,17 @@ func (b *Backend) setOffline() { } func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { - body := mustMarshalJSON(rpcReqs) + isSingleElementBatch := len(rpcReqs) == 1 + + // Single element batches are unwrapped before being sent + // since Alchemy handles single requests better than batches. + + var body []byte + if isSingleElementBatch { + body = mustMarshalJSON(rpcReqs[0]) + } else { + body = mustMarshalJSON(rpcReqs) + } httpReq, err := http.NewRequestWithContext(ctx, "POST", b.rpcURL, bytes.NewReader(body)) if err != nil { @@ -402,12 +412,22 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool } var res []*RPCRes - if err := json.Unmarshal(resB, &res); err != nil { - // Infura may return a single JSON-RPC response if, for example, the batch contains a request for an unsupported method - if responseIsNotBatched(resB) { - return nil, ErrBackendUnexpectedJSONRPC + if isSingleElementBatch { + var singleRes RPCRes + if err := json.Unmarshal(resB, &singleRes); err != nil { + return nil, ErrBackendBadResponse + } + res = []*RPCRes{ + &singleRes, + } + } else { + if err := json.Unmarshal(resB, &res); err != nil { + // Infura may return a single JSON-RPC response if, for example, the batch contains a request for an unsupported method + if responseIsNotBatched(resB) { + return nil, ErrBackendUnexpectedJSONRPC + } + return nil, ErrBackendBadResponse } - return nil, ErrBackendBadResponse } if len(rpcReqs) != len(res) { diff --git a/proxyd/proxyd/integration_tests/mock_backend_test.go b/proxyd/proxyd/integration_tests/mock_backend_test.go index b84b7d7..aa8f0ba 100644 --- a/proxyd/proxyd/integration_tests/mock_backend_test.go +++ b/proxyd/proxyd/integration_tests/mock_backend_test.go @@ -35,8 +35,12 @@ func SingleResponseHandler(code int, response string) http.HandlerFunc { } func BatchedResponseHandler(code int, responses ...string) http.HandlerFunc { - // all proxyd upstream requests are batched return func(w http.ResponseWriter, r *http.Request) { + if len(responses) == 1 { + SingleResponseHandler(code, responses[0])(w, r) + return + } + var body string body += "[" for i, response := range responses { diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go index 6a63f71..d93d3a7 100644 --- a/proxyd/proxyd/integration_tests/ws_test.go +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -44,11 +44,14 @@ func TestConcurrentWSPanic(t *testing.T) { <-readyCh + var wg sync.WaitGroup + wg.Add(2) // spam messages go func() { for { select { case <-quitC: + wg.Done() return default: _ = backendToProxyConn.WriteMessage(websocket.TextMessage, []byte("garbage")) @@ -61,6 +64,7 @@ func TestConcurrentWSPanic(t *testing.T) { for { select { case <-quitC: + wg.Done() return default: _ = client.WriteMessage(websocket.TextMessage, []byte("{\"id\": 1, \"method\": \"eth_foo\", \"params\": [\"newHeads\"]}")) @@ -72,6 +76,7 @@ func TestConcurrentWSPanic(t *testing.T) { // concurrent write to websocket connection time.Sleep(time.Second) close(quitC) + wg.Wait() } type backendHandler struct { From f3d3492a816fc96442fec640bf558be3ebb3677a Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Thu, 4 Aug 2022 11:34:43 -0600 Subject: [PATCH 014/212] proxyd: Add frontend rate limiting (#3166) * proxyd: Add frontend rate limiting To give us more flexibiltiy with rate limiting, proxyd now supports rate limiting of client (frontend) requests in addition to upstream (backend) requests. This PR also gives us the ability to exempt certain user agents/origins from rate limiting. * lint --- proxyd/proxyd/backend.go | 19 ++-- proxyd/proxyd/config.go | 8 ++ proxyd/proxyd/go.mod | 3 +- proxyd/proxyd/go.sum | 6 +- .../integration_tests/rate_limit_test.go | 79 +++++++++++++-- ...ate_limit.toml => backend_rate_limit.toml} | 0 .../testdata/frontend_rate_limit.toml | 23 +++++ proxyd/proxyd/integration_tests/util_test.go | 22 ++++- proxyd/proxyd/proxyd.go | 10 +- proxyd/proxyd/rate_limiter.go | 45 +++++---- proxyd/proxyd/rpc.go | 8 ++ proxyd/proxyd/server.go | 99 ++++++++++++++----- 12 files changed, 256 insertions(+), 66 deletions(-) rename proxyd/proxyd/integration_tests/testdata/{rate_limit.toml => backend_rate_limit.toml} (100%) create mode 100644 proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index f4d7d93..80de372 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -74,6 +74,11 @@ var ( Message: "gateway timeout", HTTPErrorCode: 504, } + ErrOverRateLimit = &RPCErr{ + Code: JSONRPCErrorInternal - 16, + Message: "rate limited", + HTTPErrorCode: 429, + } ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") ) @@ -92,7 +97,7 @@ type Backend struct { wsURL string authUsername string authPassword string - rateLimiter RateLimiter + rateLimiter BackendRateLimiter client *LimitedHTTPClient dialer *websocket.Dialer maxRetries int @@ -174,7 +179,7 @@ func NewBackend( name string, rpcURL string, wsURL string, - rateLimiter RateLimiter, + rateLimiter BackendRateLimiter, rpcSemaphore *semaphore.Weighted, opts ...BackendOpt, ) *Backend { @@ -372,10 +377,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool xForwardedFor := GetXForwardedFor(ctx) if b.stripTrailingXFF { - ipList := strings.Split(xForwardedFor, ", ") - if len(ipList) > 0 { - xForwardedFor = ipList[0] - } + xForwardedFor = stripXFF(xForwardedFor) } else if b.proxydIP != "" { xForwardedFor = fmt.Sprintf("%s, %s", xForwardedFor, b.proxydIP) } @@ -855,3 +857,8 @@ func RecordBatchRPCForward(ctx context.Context, backendName string, reqs []*RPCR RecordRPCForward(ctx, backendName, req.Method, source) } } + +func stripXFF(xff string) string { + ipList := strings.Split(xff, ", ") + return strings.TrimSpace(ipList[0]) +} diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index db46167..56eb3cc 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -39,6 +39,13 @@ type MetricsConfig struct { Port int `toml:"port"` } +type RateLimitConfig struct { + RatePerSecond int `toml:"rate_per_second"` + ExemptOrigins []string `toml:"exempt_origins"` + ExemptUserAgents []string `toml:"exempt_user_agents"` + ErrorMessage string `toml:"error_message"` +} + type BackendOptions struct { ResponseTimeoutSeconds int `toml:"response_timeout_seconds"` MaxResponseSizeBytes int64 `toml:"max_response_size_bytes"` @@ -75,6 +82,7 @@ type Config struct { Cache CacheConfig `toml:"cache"` Redis RedisConfig `toml:"redis"` Metrics MetricsConfig `toml:"metrics"` + RateLimit RateLimitConfig `toml:"rate_limit"` BackendOptions BackendOptions `toml:"backend"` Backends BackendsConfig `toml:"backends"` Authentication map[string]string `toml:"authentication"` diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 73d7d85..0a866b8 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -13,6 +13,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/prometheus/client_golang v1.11.0 github.com/rs/cors v1.8.2 + github.com/sethvargo/go-limiter v0.7.2 github.com/stretchr/testify v1.7.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c ) @@ -59,7 +60,7 @@ require ( github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect - golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect + golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index d6d5053..8e5fd8d 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -451,6 +451,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sethvargo/go-limiter v0.7.2 h1:FgC4N7RMpV5gMrUdda15FaFTkQ/L4fEqM7seXMs4oO8= +github.com/sethvargo/go-limiter v0.7.2/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 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= @@ -701,8 +703,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/proxyd/integration_tests/rate_limit_test.go index 409598e..4648017 100644 --- a/proxyd/proxyd/integration_tests/rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/rate_limit_test.go @@ -1,8 +1,11 @@ package integration_tests import ( + "fmt" + "net/http" "os" "testing" + "time" "github.com/ethereum-optimism/optimism/proxyd" "github.com/stretchr/testify/require" @@ -13,18 +16,83 @@ type resWithCode struct { res []byte } -func TestMaxRPSLimit(t *testing.T) { +const frontendOverLimitResponse = `{"error":{"code":-32016,"message":"over rate limit"},"id":null,"jsonrpc":"2.0"}` + +func TestBackendMaxRPSLimit(t *testing.T) { goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) defer goodBackend.Close() require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - config := ReadConfig("rate_limit") + config := ReadConfig("backend_rate_limit") client := NewProxydClient("http://127.0.0.1:8545") shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() + limitedRes, codes := spamReqs(t, client, 503) + require.Equal(t, 2, codes[200]) + require.Equal(t, 1, codes[503]) + RequireEqualJSON(t, []byte(noBackendsResponse), limitedRes) +} + +func TestFrontendMaxRPSLimit(t *testing.T) { + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) + defer goodBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + config := ReadConfig("frontend_rate_limit") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + t.Run("non-exempt over limit", func(t *testing.T) { + client := NewProxydClient("http://127.0.0.1:8545") + limitedRes, codes := spamReqs(t, client, 429) + require.Equal(t, 1, codes[429]) + require.Equal(t, 2, codes[200]) + RequireEqualJSON(t, []byte(frontendOverLimitResponse), limitedRes) + }) + + t.Run("exempt user agent over limit", func(t *testing.T) { + h := make(http.Header) + h.Set("User-Agent", "exempt_agent") + client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) + _, codes := spamReqs(t, client, 429) + require.Equal(t, 3, codes[200]) + }) + + t.Run("exempt origin over limit", func(t *testing.T) { + h := make(http.Header) + h.Set("Origin", "exempt_origin") + client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) + _, codes := spamReqs(t, client, 429) + fmt.Println(codes) + require.Equal(t, 3, codes[200]) + }) + + t.Run("multiple xff", func(t *testing.T) { + h1 := make(http.Header) + h1.Set("X-Forwarded-For", "0.0.0.0") + h2 := make(http.Header) + h2.Set("X-Forwarded-For", "1.1.1.1") + client1 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h1) + client2 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h2) + _, codes := spamReqs(t, client1, 429) + require.Equal(t, 1, codes[429]) + require.Equal(t, 2, codes[200]) + _, code, err := client2.SendRPC("eth_chainId", nil) + require.Equal(t, 200, code) + require.NoError(t, err) + time.Sleep(time.Second) + _, code, err = client2.SendRPC("eth_chainId", nil) + require.Equal(t, 200, code) + require.NoError(t, err) + }) +} + +func spamReqs(t *testing.T, client *ProxydHTTPClient, limCode int) ([]byte, map[int]int) { resCh := make(chan *resWithCode) for i := 0; i < 3; i++ { go func() { @@ -48,13 +116,10 @@ func TestMaxRPSLimit(t *testing.T) { codes[code] += 1 } - // 503 because there's only one backend available - if code == 503 { + if code == limCode { limitedRes = res.res } } - require.Equal(t, 2, codes[200]) - require.Equal(t, 1, codes[503]) - RequireEqualJSON(t, []byte(noBackendsResponse), limitedRes) + return limitedRes, codes } diff --git a/proxyd/proxyd/integration_tests/testdata/rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/rate_limit.toml rename to proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml diff --git a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml new file mode 100644 index 0000000..e838ace --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml @@ -0,0 +1,23 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" + +[rate_limit] +rate_per_second = 2 +exempt_origins = ["exempt_origin"] +exempt_user_agents = ["exempt_agent"] +error_message = "over rate limit" diff --git a/proxyd/proxyd/integration_tests/util_test.go b/proxyd/proxyd/integration_tests/util_test.go index c5c15a6..2e443f6 100644 --- a/proxyd/proxyd/integration_tests/util_test.go +++ b/proxyd/proxyd/integration_tests/util_test.go @@ -20,11 +20,21 @@ import ( ) type ProxydHTTPClient struct { - url string + url string + headers http.Header } func NewProxydClient(url string) *ProxydHTTPClient { - return &ProxydHTTPClient{url: url} + return NewProxydClientWithHeaders(url, make(http.Header)) +} + +func NewProxydClientWithHeaders(url string, headers http.Header) *ProxydHTTPClient { + clonedHeaders := headers.Clone() + clonedHeaders.Set("Content-Type", "application/json") + return &ProxydHTTPClient{ + url: url, + headers: clonedHeaders, + } } func (p *ProxydHTTPClient) SendRPC(method string, params []interface{}) ([]byte, int, error) { @@ -45,7 +55,13 @@ func (p *ProxydHTTPClient) SendBatchRPC(reqs ...*proxyd.RPCReq) ([]byte, int, er } func (p *ProxydHTTPClient) SendRequest(body []byte) ([]byte, int, error) { - res, err := http.Post(p.url, "application/json", bytes.NewReader(body)) + req, err := http.NewRequest("POST", p.url, bytes.NewReader(body)) + if err != nil { + panic(err) + } + req.Header = p.headers + + res, err := http.DefaultClient.Do(req) if err != nil { return nil, -1, err } diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 4b616c5..e9bbe42 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -43,11 +43,11 @@ func Start(config *Config) (func(), error) { redisURL = rURL } - var lim RateLimiter + var lim BackendRateLimiter var err error if redisURL == "" { log.Warn("redis is not configured, using local rate limiter") - lim = NewLocalRateLimiter() + lim = NewLocalBackendRateLimiter() } else { lim, err = NewRedisRateLimiter(redisURL) if err != nil { @@ -212,7 +212,7 @@ func Start(config *Config) (func(), error) { rpcCache = newRPCCache(newCacheWithCompression(cache), blockNumFn, gasPriceFn, config.Cache.NumBlockConfirmations) } - srv := NewServer( + srv, err := NewServer( backendGroups, wsBackendGroup, NewStringSetFromStrings(config.WSMethodWhitelist), @@ -222,9 +222,13 @@ func Start(config *Config) (func(), error) { secondsToDuration(config.Server.TimeoutSeconds), config.Server.MaxUpstreamBatchSize, rpcCache, + config.RateLimit, config.Server.EnableRequestLog, config.Server.MaxRequestBodyLogLen, ) + if err != nil { + return nil, fmt.Errorf("error creating server: %w", err) + } if config.Metrics.Enabled { addr := fmt.Sprintf("%s:%d", config.Metrics.Host, config.Metrics.Port) diff --git a/proxyd/proxyd/rate_limiter.go b/proxyd/proxyd/rate_limiter.go index 5c4a4d6..fe286e6 100644 --- a/proxyd/proxyd/rate_limiter.go +++ b/proxyd/proxyd/rate_limiter.go @@ -41,7 +41,7 @@ end return false ` -type RateLimiter interface { +type BackendRateLimiter interface { IsBackendOnline(name string) (bool, error) SetBackendOffline(name string, duration time.Duration) error IncBackendRPS(name string) (int, error) @@ -50,14 +50,14 @@ type RateLimiter interface { FlushBackendWSConns(names []string) error } -type RedisRateLimiter struct { +type RedisBackendRateLimiter struct { rdb *redis.Client randID string touchKeys map[string]time.Duration tkMtx sync.Mutex } -func NewRedisRateLimiter(url string) (RateLimiter, error) { +func NewRedisRateLimiter(url string) (BackendRateLimiter, error) { opts, err := redis.ParseURL(url) if err != nil { return nil, err @@ -66,7 +66,7 @@ func NewRedisRateLimiter(url string) (RateLimiter, error) { if err := rdb.Ping(context.Background()).Err(); err != nil { return nil, wrapErr(err, "error connecting to redis") } - out := &RedisRateLimiter{ + out := &RedisBackendRateLimiter{ rdb: rdb, randID: randStr(20), touchKeys: make(map[string]time.Duration), @@ -75,7 +75,7 @@ func NewRedisRateLimiter(url string) (RateLimiter, error) { return out, nil } -func (r *RedisRateLimiter) IsBackendOnline(name string) (bool, error) { +func (r *RedisBackendRateLimiter) IsBackendOnline(name string) (bool, error) { exists, err := r.rdb.Exists(context.Background(), fmt.Sprintf("backend:%s:offline", name)).Result() if err != nil { RecordRedisError("IsBackendOnline") @@ -85,7 +85,7 @@ func (r *RedisRateLimiter) IsBackendOnline(name string) (bool, error) { return exists == 0, nil } -func (r *RedisRateLimiter) SetBackendOffline(name string, duration time.Duration) error { +func (r *RedisBackendRateLimiter) SetBackendOffline(name string, duration time.Duration) error { if duration == 0 { return nil } @@ -102,7 +102,7 @@ func (r *RedisRateLimiter) SetBackendOffline(name string, duration time.Duration return nil } -func (r *RedisRateLimiter) IncBackendRPS(name string) (int, error) { +func (r *RedisBackendRateLimiter) IncBackendRPS(name string) (int, error) { cmd := r.rdb.Eval( context.Background(), MaxRPSScript, @@ -116,7 +116,7 @@ func (r *RedisRateLimiter) IncBackendRPS(name string) (int, error) { return rps, nil } -func (r *RedisRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { +func (r *RedisBackendRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) r.tkMtx.Lock() r.touchKeys[connsKey] = 5 * time.Minute @@ -142,7 +142,7 @@ func (r *RedisRateLimiter) IncBackendWSConns(name string, max int) (bool, error) return incremented, nil } -func (r *RedisRateLimiter) DecBackendWSConns(name string) error { +func (r *RedisBackendRateLimiter) DecBackendWSConns(name string) error { connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) err := r.rdb.Decr(context.Background(), connsKey).Err() if err != nil { @@ -152,7 +152,7 @@ func (r *RedisRateLimiter) DecBackendWSConns(name string) error { return nil } -func (r *RedisRateLimiter) FlushBackendWSConns(names []string) error { +func (r *RedisBackendRateLimiter) FlushBackendWSConns(names []string) error { ctx := context.Background() for _, name := range names { connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) @@ -172,7 +172,7 @@ func (r *RedisRateLimiter) FlushBackendWSConns(names []string) error { return nil } -func (r *RedisRateLimiter) touch() { +func (r *RedisBackendRateLimiter) touch() { for { r.tkMtx.Lock() for key, dur := range r.touchKeys { @@ -186,15 +186,15 @@ func (r *RedisRateLimiter) touch() { } } -type LocalRateLimiter struct { +type LocalBackendRateLimiter struct { deadBackends map[string]time.Time backendRPS map[string]int backendWSConns map[string]int mtx sync.RWMutex } -func NewLocalRateLimiter() *LocalRateLimiter { - out := &LocalRateLimiter{ +func NewLocalBackendRateLimiter() *LocalBackendRateLimiter { + out := &LocalBackendRateLimiter{ deadBackends: make(map[string]time.Time), backendRPS: make(map[string]int), backendWSConns: make(map[string]int), @@ -203,27 +203,27 @@ func NewLocalRateLimiter() *LocalRateLimiter { return out } -func (l *LocalRateLimiter) IsBackendOnline(name string) (bool, error) { +func (l *LocalBackendRateLimiter) IsBackendOnline(name string) (bool, error) { l.mtx.RLock() defer l.mtx.RUnlock() return l.deadBackends[name].Before(time.Now()), nil } -func (l *LocalRateLimiter) SetBackendOffline(name string, duration time.Duration) error { +func (l *LocalBackendRateLimiter) SetBackendOffline(name string, duration time.Duration) error { l.mtx.Lock() defer l.mtx.Unlock() l.deadBackends[name] = time.Now().Add(duration) return nil } -func (l *LocalRateLimiter) IncBackendRPS(name string) (int, error) { +func (l *LocalBackendRateLimiter) IncBackendRPS(name string) (int, error) { l.mtx.Lock() defer l.mtx.Unlock() l.backendRPS[name] += 1 return l.backendRPS[name], nil } -func (l *LocalRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { +func (l *LocalBackendRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { l.mtx.Lock() defer l.mtx.Unlock() if l.backendWSConns[name] == max { @@ -233,7 +233,7 @@ func (l *LocalRateLimiter) IncBackendWSConns(name string, max int) (bool, error) return true, nil } -func (l *LocalRateLimiter) DecBackendWSConns(name string) error { +func (l *LocalBackendRateLimiter) DecBackendWSConns(name string) error { l.mtx.Lock() defer l.mtx.Unlock() if l.backendWSConns[name] == 0 { @@ -243,11 +243,11 @@ func (l *LocalRateLimiter) DecBackendWSConns(name string) error { return nil } -func (l *LocalRateLimiter) FlushBackendWSConns(names []string) error { +func (l *LocalBackendRateLimiter) FlushBackendWSConns(names []string) error { return nil } -func (l *LocalRateLimiter) clear() { +func (l *LocalBackendRateLimiter) clear() { for { time.Sleep(time.Second) l.mtx.Lock() @@ -263,3 +263,6 @@ func randStr(l int) string { } return hex.EncodeToString(b) } + +type ServerRateLimiter struct { +} diff --git a/proxyd/proxyd/rpc.go b/proxyd/proxyd/rpc.go index 5f16822..ccd7c5f 100644 --- a/proxyd/proxyd/rpc.go +++ b/proxyd/proxyd/rpc.go @@ -65,6 +65,14 @@ func (r *RPCErr) Error() string { return r.Message } +func (r *RPCErr) Clone() *RPCErr { + return &RPCErr{ + Code: r.Code, + Message: r.Message, + HTTPErrorCode: r.HTTPErrorCode, + } +} + func IsValidID(id json.RawMessage) bool { // handle the case where the ID is a string if strings.HasPrefix(string(id), "\"") && strings.HasSuffix(string(id), "\"") { diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 58cd739..27f3f0e 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -14,6 +14,10 @@ import ( "sync" "time" + "github.com/sethvargo/go-limiter" + "github.com/sethvargo/go-limiter/memorystore" + "github.com/sethvargo/go-limiter/noopstore" + "github.com/ethereum/go-ethereum/log" "github.com/gorilla/mux" "github.com/gorilla/websocket" @@ -46,6 +50,10 @@ type Server struct { timeout time.Duration maxUpstreamBatchSize int upgrader *websocket.Upgrader + lim limiter.Store + limConfig RateLimitConfig + limExemptOrigins map[string]bool + limExemptUserAgents map[string]bool rpcServer *http.Server wsServer *http.Server cache RPCCache @@ -62,9 +70,10 @@ func NewServer( timeout time.Duration, maxUpstreamBatchSize int, cache RPCCache, + rateLimitConfig RateLimitConfig, enableRequestLog bool, maxRequestBodyLogLen int, -) *Server { +) (*Server, error) { if cache == nil { cache = &NoopRPCCache{} } @@ -81,6 +90,29 @@ func NewServer( maxUpstreamBatchSize = defaultMaxUpstreamBatchSize } + var lim limiter.Store + limExemptOrigins := make(map[string]bool) + limExemptUserAgents := make(map[string]bool) + if rateLimitConfig.RatePerSecond > 0 { + var err error + lim, err = memorystore.New(&memorystore.Config{ + Tokens: uint64(rateLimitConfig.RatePerSecond), + Interval: time.Second, + }) + if err != nil { + return nil, err + } + + for _, origin := range rateLimitConfig.ExemptOrigins { + limExemptOrigins[strings.ToLower(origin)] = true + } + for _, agent := range rateLimitConfig.ExemptUserAgents { + limExemptUserAgents[strings.ToLower(agent)] = true + } + } else { + lim, _ = noopstore.New() + } + return &Server{ backendGroups: backendGroups, wsBackendGroup: wsBackendGroup, @@ -96,7 +128,11 @@ func NewServer( upgrader: &websocket.Upgrader{ HandshakeTimeout: 5 * time.Second, }, - } + lim: lim, + limConfig: rateLimitConfig, + limExemptOrigins: limExemptOrigins, + limExemptUserAgents: limExemptUserAgents, + }, nil } func (s *Server) RPCListenAndServe(host string, port int) error { @@ -160,6 +196,28 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { ctx, cancel = context.WithTimeout(ctx, s.timeout) defer cancel() + exemptOrigin := s.limExemptOrigins[strings.ToLower(r.Header.Get("Origin"))] + exemptUserAgent := s.limExemptUserAgents[strings.ToLower(r.Header.Get("User-Agent"))] + var ok bool + if exemptOrigin || exemptUserAgent { + ok = true + } else { + // Use XFF in context since it will automatically be replaced by the remote IP + xff := stripXFF(GetXForwardedFor(ctx)) + if xff == "" { + log.Warn("rejecting request without XFF or remote IP") + ok = false + } else { + _, _, _, ok, _ = s.lim.Take(ctx, xff) + } + } + if !ok { + rpcErr := ErrOverRateLimit.Clone() + rpcErr.Message = s.limConfig.ErrorMessage + writeRPCError(ctx, w, nil, rpcErr) + return + } + log.Info( "received RPC request", "req_id", GetReqID(ctx), @@ -390,6 +448,14 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context.Context { vars := mux.Vars(r) authorization := vars["authorization"] + xff := r.Header.Get("X-Forwarded-For") + if xff == "" { + ipPort := strings.Split(r.RemoteAddr, ":") + if len(ipPort) == 2 { + xff = ipPort[0] + } + } + ctx := context.WithValue(r.Context(), ContextKeyXForwardedFor, xff) // nolint:staticcheck if s.authenticatedPaths == nil { // handle the edge case where auth is disabled @@ -400,30 +466,17 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context w.WriteHeader(404) return nil } - return context.WithValue( - r.Context(), - ContextKeyReqID, // nolint:staticcheck - randStr(10), - ) - } - - if authorization == "" || s.authenticatedPaths[authorization] == "" { - log.Info("blocked unauthorized request", "authorization", authorization) - httpResponseCodesTotal.WithLabelValues("401").Inc() - w.WriteHeader(401) - return nil - } - - xff := r.Header.Get("X-Forwarded-For") - if xff == "" { - ipPort := strings.Split(r.RemoteAddr, ":") - if len(ipPort) == 2 { - xff = ipPort[0] + } else { + if authorization == "" || s.authenticatedPaths[authorization] == "" { + log.Info("blocked unauthorized request", "authorization", authorization) + httpResponseCodesTotal.WithLabelValues("401").Inc() + w.WriteHeader(401) + return nil } + + ctx = context.WithValue(r.Context(), ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck } - ctx := context.WithValue(r.Context(), ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck - ctx = context.WithValue(ctx, ContextKeyXForwardedFor, xff) // nolint:staticcheck return context.WithValue( ctx, ContextKeyReqID, // nolint:staticcheck From 7c599f758f9912dbc43d7bd15ec3eebc7541bed0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 Aug 2022 17:40:22 +0000 Subject: [PATCH 015/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 11 +++++++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 1c5e6a2..2bf4583 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,16 @@ # @eth-optimism/proxyd +## 3.9.0 + +### Minor Changes + +- b6f4bfcf: Add frontend rate limiting + +### Patch Changes + +- 406a4fce: Unwrap single RPC batches +- 915f3b28: Parameterize full RPC request logging + ## 3.8.9 ### Patch Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 8192dc8..721748b 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.8.9", + "version": "3.9.0", "private": true, "dependencies": {} } From 7a2b4bdf45af8b3ce453832f9452756d317778bf Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Thu, 4 Aug 2022 12:44:46 -0600 Subject: [PATCH 016/212] proxyd: Add rate limit logging (#3173) --- .../integration_tests/rate_limit_test.go | 2 -- proxyd/proxyd/server.go | 21 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/proxyd/integration_tests/rate_limit_test.go index 4648017..310be22 100644 --- a/proxyd/proxyd/integration_tests/rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/rate_limit_test.go @@ -1,7 +1,6 @@ package integration_tests import ( - "fmt" "net/http" "os" "testing" @@ -68,7 +67,6 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h.Set("Origin", "exempt_origin") client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) _, codes := spamReqs(t, client, 429) - fmt.Println(codes) require.Equal(t, 3, codes[200]) }) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 27f3f0e..5f76e3e 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -196,14 +196,16 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { ctx, cancel = context.WithTimeout(ctx, s.timeout) defer cancel() - exemptOrigin := s.limExemptOrigins[strings.ToLower(r.Header.Get("Origin"))] - exemptUserAgent := s.limExemptUserAgents[strings.ToLower(r.Header.Get("User-Agent"))] + origin := r.Header.Get("Origin") + userAgent := r.Header.Get("User-Agent") + exemptOrigin := s.limExemptOrigins[strings.ToLower(origin)] + exemptUserAgent := s.limExemptUserAgents[strings.ToLower(userAgent)] + // Use XFF in context since it will automatically be replaced by the remote IP + xff := stripXFF(GetXForwardedFor(ctx)) var ok bool if exemptOrigin || exemptUserAgent { ok = true } else { - // Use XFF in context since it will automatically be replaced by the remote IP - xff := stripXFF(GetXForwardedFor(ctx)) if xff == "" { log.Warn("rejecting request without XFF or remote IP") ok = false @@ -214,6 +216,15 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { if !ok { rpcErr := ErrOverRateLimit.Clone() rpcErr.Message = s.limConfig.ErrorMessage + RecordRPCError(ctx, BackendProxyd, "unknown", rpcErr) + log.Warn( + "rate limited request", + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + "user_agent", userAgent, + "origin", origin, + "remote_ip", xff, + ) writeRPCError(ctx, w, nil, rpcErr) return } @@ -222,7 +233,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { "received RPC request", "req_id", GetReqID(ctx), "auth", GetAuthCtx(ctx), - "user_agent", r.Header.Get("user-agent"), + "user_agent", userAgent, ) body, err := ioutil.ReadAll(io.LimitReader(r.Body, s.maxBodySize)) From fc9487bf835b5ae501225e055ae1bb796bdefd69 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 Aug 2022 18:48:19 +0000 Subject: [PATCH 017/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 6 ++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 2bf4583..ac6a0c3 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,11 @@ # @eth-optimism/proxyd +## 3.9.1 + +### Patch Changes + +- dc4f6a06: Add logging/metrics + ## 3.9.0 ### Minor Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 721748b..d078d75 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.9.0", + "version": "3.9.1", "private": true, "dependencies": {} } From 9a4626856a536b8980ffd05c119b6b72063b8104 Mon Sep 17 00:00:00 2001 From: protolambda Date: Sat, 27 Aug 2022 03:39:30 +0200 Subject: [PATCH 018/212] style(batch-submitter,bss-core,proxyd): Fix lint Go (#3328) * style(batch-submitter): fix lint * style(bss-core): fix lint * chore(proxyd): use io and os instead of deprecated ioutil methods, fixes lint --- proxyd/proxyd/backend.go | 3 +-- proxyd/proxyd/integration_tests/mock_backend_test.go | 8 ++++---- proxyd/proxyd/integration_tests/util_test.go | 4 ++-- proxyd/proxyd/rpc.go | 3 +-- proxyd/proxyd/server.go | 3 +-- proxyd/proxyd/tls.go | 4 ++-- 6 files changed, 11 insertions(+), 14 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 80de372..9a004d0 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "math" "math/rand" "net/http" @@ -408,7 +407,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool } defer httpRes.Body.Close() - resB, err := ioutil.ReadAll(io.LimitReader(httpRes.Body, b.maxResponseSize)) + resB, err := io.ReadAll(io.LimitReader(httpRes.Body, b.maxResponseSize)) if err != nil { return nil, wrapErr(err, "error reading response body") } diff --git a/proxyd/proxyd/integration_tests/mock_backend_test.go b/proxyd/proxyd/integration_tests/mock_backend_test.go index aa8f0ba..ade879c 100644 --- a/proxyd/proxyd/integration_tests/mock_backend_test.go +++ b/proxyd/proxyd/integration_tests/mock_backend_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/json" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" @@ -122,7 +122,7 @@ func (h *BatchRPCResponseRouter) ServeHTTP(w http.ResponseWriter, r *http.Reques h.mtx.Lock() defer h.mtx.Unlock() - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { panic(err) } @@ -241,12 +241,12 @@ func (m *MockBackend) Requests() []*RecordedRequest { func (m *MockBackend) wrappedHandler(w http.ResponseWriter, r *http.Request) { m.mtx.Lock() - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { panic(err) } clone := r.Clone(context.Background()) - clone.Body = ioutil.NopCloser(bytes.NewReader(body)) + clone.Body = io.NopCloser(bytes.NewReader(body)) m.requests = append(m.requests, &RecordedRequest{ Method: r.Method, Headers: r.Header.Clone(), diff --git a/proxyd/proxyd/integration_tests/util_test.go b/proxyd/proxyd/integration_tests/util_test.go index 2e443f6..db52d2f 100644 --- a/proxyd/proxyd/integration_tests/util_test.go +++ b/proxyd/proxyd/integration_tests/util_test.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "os" "testing" @@ -67,7 +67,7 @@ func (p *ProxydHTTPClient) SendRequest(body []byte) ([]byte, int, error) { } defer res.Body.Close() code := res.StatusCode - resBody, err := ioutil.ReadAll(res.Body) + resBody, err := io.ReadAll(res.Body) if err != nil { panic(err) } diff --git a/proxyd/proxyd/rpc.go b/proxyd/proxyd/rpc.go index ccd7c5f..e34b010 100644 --- a/proxyd/proxyd/rpc.go +++ b/proxyd/proxyd/rpc.go @@ -3,7 +3,6 @@ package proxyd import ( "encoding/json" "io" - "io/ioutil" "strings" ) @@ -103,7 +102,7 @@ func ParseBatchRPCReq(body []byte) ([]json.RawMessage, error) { } func ParseRPCRes(r io.Reader) (*RPCRes, error) { - body, err := ioutil.ReadAll(r) + body, err := io.ReadAll(r) if err != nil { return nil, wrapErr(err, "error reading RPC response") } diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 5f76e3e..86d7d5f 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "math" "net/http" "strconv" @@ -236,7 +235,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { "user_agent", userAgent, ) - body, err := ioutil.ReadAll(io.LimitReader(r.Body, s.maxBodySize)) + body, err := io.ReadAll(io.LimitReader(r.Body, s.maxBodySize)) if err != nil { log.Error("error reading request body", "err", err) writeRPCError(ctx, w, nil, ErrInternal) diff --git a/proxyd/proxyd/tls.go b/proxyd/proxyd/tls.go index 34b214c..ed2bdaf 100644 --- a/proxyd/proxyd/tls.go +++ b/proxyd/proxyd/tls.go @@ -4,11 +4,11 @@ import ( "crypto/tls" "crypto/x509" "errors" - "io/ioutil" + "os" ) func CreateTLSClient(ca string) (*tls.Config, error) { - pem, err := ioutil.ReadFile(ca) + pem, err := os.ReadFile(ca) if err != nil { return nil, wrapErr(err, "error reading CA") } From 8e92c91e6d578983e162e9ae4a9b975e4501d46c Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Fri, 9 Sep 2022 04:56:38 -0700 Subject: [PATCH 019/212] Last unwrapped errors These where missed in local lints but when a full lint was run these popped up. --- proxyd/proxyd/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/errors.go b/proxyd/proxyd/errors.go index 97a7ebc..51f8df6 100644 --- a/proxyd/proxyd/errors.go +++ b/proxyd/proxyd/errors.go @@ -3,5 +3,5 @@ package proxyd import "fmt" func wrapErr(err error, msg string) error { - return fmt.Errorf("%s %v", msg, err) + return fmt.Errorf("%s %w", msg, err) } From ccf093445949c27cec12f134169969a472a60da7 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Thu, 15 Sep 2022 10:46:11 +0200 Subject: [PATCH 020/212] proxyd: Support per-RPC rate limits (#3471) * proxyd: Support per-RPC rate limits * add log Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- proxyd/proxyd/config.go | 27 +++++- .../integration_tests/rate_limit_test.go | 70 +++++++++++-- .../testdata/frontend_rate_limit.toml | 5 + proxyd/proxyd/server.go | 97 +++++++++++++++---- 4 files changed, 164 insertions(+), 35 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 56eb3cc..ba60d67 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "strings" + "time" ) type ServerConfig struct { @@ -40,10 +41,28 @@ type MetricsConfig struct { } type RateLimitConfig struct { - RatePerSecond int `toml:"rate_per_second"` - ExemptOrigins []string `toml:"exempt_origins"` - ExemptUserAgents []string `toml:"exempt_user_agents"` - ErrorMessage string `toml:"error_message"` + RatePerSecond int `toml:"rate_per_second"` + ExemptOrigins []string `toml:"exempt_origins"` + ExemptUserAgents []string `toml:"exempt_user_agents"` + ErrorMessage string `toml:"error_message"` + MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` +} + +type RateLimitMethodOverride struct { + Limit int `toml:"limit"` + Interval TOMLDuration `toml:"interval"` +} + +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 } type BackendOptions struct { diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/proxyd/integration_tests/rate_limit_test.go index 310be22..5d3163a 100644 --- a/proxyd/proxyd/integration_tests/rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/rate_limit_test.go @@ -1,6 +1,7 @@ package integration_tests import ( + "encoding/json" "net/http" "os" "testing" @@ -17,6 +18,8 @@ type resWithCode struct { const frontendOverLimitResponse = `{"error":{"code":-32016,"message":"over rate limit"},"id":null,"jsonrpc":"2.0"}` +var ethChainID = "eth_chainId" + func TestBackendMaxRPSLimit(t *testing.T) { goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) defer goodBackend.Close() @@ -28,8 +31,7 @@ func TestBackendMaxRPSLimit(t *testing.T) { shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() - - limitedRes, codes := spamReqs(t, client, 503) + limitedRes, codes := spamReqs(t, client, ethChainID, 503) require.Equal(t, 2, codes[200]) require.Equal(t, 1, codes[503]) RequireEqualJSON(t, []byte(noBackendsResponse), limitedRes) @@ -48,7 +50,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { t.Run("non-exempt over limit", func(t *testing.T) { client := NewProxydClient("http://127.0.0.1:8545") - limitedRes, codes := spamReqs(t, client, 429) + limitedRes, codes := spamReqs(t, client, ethChainID, 429) require.Equal(t, 1, codes[429]) require.Equal(t, 2, codes[200]) RequireEqualJSON(t, []byte(frontendOverLimitResponse), limitedRes) @@ -58,7 +60,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h := make(http.Header) h.Set("User-Agent", "exempt_agent") client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - _, codes := spamReqs(t, client, 429) + _, codes := spamReqs(t, client, ethChainID, 429) require.Equal(t, 3, codes[200]) }) @@ -66,7 +68,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h := make(http.Header) h.Set("Origin", "exempt_origin") client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - _, codes := spamReqs(t, client, 429) + _, codes := spamReqs(t, client, ethChainID, 429) require.Equal(t, 3, codes[200]) }) @@ -77,24 +79,72 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h2.Set("X-Forwarded-For", "1.1.1.1") client1 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h1) client2 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h2) - _, codes := spamReqs(t, client1, 429) + _, codes := spamReqs(t, client1, ethChainID, 429) require.Equal(t, 1, codes[429]) require.Equal(t, 2, codes[200]) - _, code, err := client2.SendRPC("eth_chainId", nil) + _, code, err := client2.SendRPC(ethChainID, nil) require.Equal(t, 200, code) require.NoError(t, err) time.Sleep(time.Second) - _, code, err = client2.SendRPC("eth_chainId", nil) + _, code, err = client2.SendRPC(ethChainID, nil) require.Equal(t, 200, code) require.NoError(t, err) }) + + time.Sleep(time.Second) + + t.Run("RPC override", func(t *testing.T) { + client := NewProxydClient("http://127.0.0.1:8545") + limitedRes, codes := spamReqs(t, client, "eth_foobar", 429) + // use 2 and 1 here since the limit for eth_foobar is 1 + require.Equal(t, 2, codes[429]) + require.Equal(t, 1, codes[200]) + RequireEqualJSON(t, []byte(frontendOverLimitResponse), limitedRes) + }) + + time.Sleep(time.Second) + + t.Run("RPC override in batch", func(t *testing.T) { + client := NewProxydClient("http://127.0.0.1:8545") + req := NewRPCReq("123", "eth_foobar", nil) + out, code, err := client.SendBatchRPC(req, req, req) + require.NoError(t, err) + var res []proxyd.RPCRes + require.NoError(t, json.Unmarshal(out, &res)) + + expCode := proxyd.ErrOverRateLimit.Code + require.Equal(t, 200, code) + require.Equal(t, 3, len(res)) + require.Nil(t, res[0].Error) + require.Equal(t, expCode, res[1].Error.Code) + require.Equal(t, expCode, res[2].Error.Code) + }) + + time.Sleep(time.Second) + + t.Run("RPC override in batch exempt", func(t *testing.T) { + h := make(http.Header) + h.Set("User-Agent", "exempt_agent") + client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) + req := NewRPCReq("123", "eth_foobar", nil) + out, code, err := client.SendBatchRPC(req, req, req) + require.NoError(t, err) + var res []proxyd.RPCRes + require.NoError(t, json.Unmarshal(out, &res)) + + require.Equal(t, 200, code) + require.Equal(t, 3, len(res)) + require.Nil(t, res[0].Error) + require.Nil(t, res[1].Error) + require.Nil(t, res[2].Error) + }) } -func spamReqs(t *testing.T, client *ProxydHTTPClient, limCode int) ([]byte, map[int]int) { +func spamReqs(t *testing.T, client *ProxydHTTPClient, method string, limCode int) ([]byte, map[int]int) { resCh := make(chan *resWithCode) for i := 0; i < 3; i++ { go func() { - res, code, err := client.SendRPC("eth_chainId", nil) + res, code, err := client.SendRPC(method, nil) require.NoError(t, err) resCh <- &resWithCode{ code: code, diff --git a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml index e838ace..8615f3f 100644 --- a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml @@ -15,9 +15,14 @@ backends = ["good"] [rpc_method_mappings] eth_chainId = "main" +eth_foobar = "main" [rate_limit] rate_per_second = 2 exempt_origins = ["exempt_origin"] exempt_user_agents = ["exempt_agent"] error_message = "over rate limit" + +[rate_limit.method_overrides.eth_foobar] +limit = 1 +interval = "1s" \ No newline at end of file diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 86d7d5f..b3ff52e 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -49,7 +49,8 @@ type Server struct { timeout time.Duration maxUpstreamBatchSize int upgrader *websocket.Upgrader - lim limiter.Store + mainLim limiter.Store + overrideLims map[string]limiter.Store limConfig RateLimitConfig limExemptOrigins map[string]bool limExemptUserAgents map[string]bool @@ -59,6 +60,8 @@ type Server struct { srvMu sync.Mutex } +type limiterFunc func(method string) bool + func NewServer( backendGroups map[string]*BackendGroup, wsBackendGroup *BackendGroup, @@ -89,12 +92,12 @@ func NewServer( maxUpstreamBatchSize = defaultMaxUpstreamBatchSize } - var lim limiter.Store + var mainLim limiter.Store limExemptOrigins := make(map[string]bool) limExemptUserAgents := make(map[string]bool) if rateLimitConfig.RatePerSecond > 0 { var err error - lim, err = memorystore.New(&memorystore.Config{ + mainLim, err = memorystore.New(&memorystore.Config{ Tokens: uint64(rateLimitConfig.RatePerSecond), Interval: time.Second, }) @@ -109,7 +112,19 @@ func NewServer( limExemptUserAgents[strings.ToLower(agent)] = true } } else { - lim, _ = noopstore.New() + mainLim, _ = noopstore.New() + } + + overrideLims := make(map[string]limiter.Store) + for method, override := range rateLimitConfig.MethodOverrides { + var err error + overrideLims[method], err = memorystore.New(&memorystore.Config{ + Tokens: uint64(override.Limit), + Interval: time.Duration(override.Interval), + }) + if err != nil { + return nil, err + } } return &Server{ @@ -127,7 +142,8 @@ func NewServer( upgrader: &websocket.Upgrader{ HandshakeTimeout: 5 * time.Second, }, - lim: lim, + mainLim: mainLim, + overrideLims: overrideLims, limConfig: rateLimitConfig, limExemptOrigins: limExemptOrigins, limExemptUserAgents: limExemptUserAgents, @@ -197,22 +213,37 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") userAgent := r.Header.Get("User-Agent") - exemptOrigin := s.limExemptOrigins[strings.ToLower(origin)] - exemptUserAgent := s.limExemptUserAgents[strings.ToLower(userAgent)] // Use XFF in context since it will automatically be replaced by the remote IP xff := stripXFF(GetXForwardedFor(ctx)) - var ok bool - if exemptOrigin || exemptUserAgent { - ok = true - } else { - if xff == "" { - log.Warn("rejecting request without XFF or remote IP") - ok = false - } else { - _, _, _, ok, _ = s.lim.Take(ctx, xff) - } + isUnlimitedOrigin := s.isUnlimitedOrigin(origin) + isUnlimitedUserAgent := s.isUnlimitedUserAgent(userAgent) + + if xff == "" { + writeRPCError(ctx, w, nil, ErrInvalidRequest("request does not include a remote IP")) + return } - if !ok { + + isLimited := func(method string) bool { + if isUnlimitedOrigin || isUnlimitedUserAgent { + return false + } + + var lim limiter.Store + if method == "" { + lim = s.mainLim + } else { + lim = s.overrideLims[method] + } + + if lim == nil { + return false + } + + _, _, _, ok, _ := lim.Take(ctx, xff) + return !ok + } + + if isLimited("") { rpcErr := ErrOverRateLimit.Clone() rpcErr.Message = s.limConfig.ErrorMessage RecordRPCError(ctx, BackendProxyd, "unknown", rpcErr) @@ -271,7 +302,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { return } - batchRes, batchContainsCached, err := s.handleBatchRPC(ctx, reqs, true) + batchRes, batchContainsCached, err := s.handleBatchRPC(ctx, reqs, isLimited, true) if err == context.DeadlineExceeded { writeRPCError(ctx, w, nil, ErrGatewayTimeout) return @@ -287,7 +318,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { } rawBody := json.RawMessage(body) - backendRes, cached, err := s.handleBatchRPC(ctx, []json.RawMessage{rawBody}, false) + backendRes, cached, err := s.handleBatchRPC(ctx, []json.RawMessage{rawBody}, isLimited, false) if err != nil { writeRPCError(ctx, w, nil, ErrInternal) return @@ -296,7 +327,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { writeRPCRes(ctx, w, backendRes[0]) } -func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isBatch bool) ([]*RPCRes, bool, error) { +func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isLimited limiterFunc, isBatch bool) ([]*RPCRes, bool, error) { // A request set is transformed into groups of batches. // Each batch group maps to a forwarded JSON-RPC batch request (subject to maxUpstreamBatchSize constraints) // A groupID is used to decouple Requests that have duplicate ID so they're not part of the same batch that's @@ -347,6 +378,22 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isB continue } + // Take rate limit for specific methods. + // NOTE: eventually, this should apply to all batch requests. However, + // since we don't have data right now on the size of each batch, we + // only apply this to the methods that have an additional rate limit. + if _, ok := s.overrideLims[parsedReq.Method]; ok && isLimited(parsedReq.Method) { + log.Info( + "rate limited specific RPC", + "source", "rpc", + "req_id", GetReqID(ctx), + "method", parsedReq.Method, + ) + RecordRPCError(ctx, BackendProxyd, parsedReq.Method, ErrOverRateLimit) + responses[i] = NewRPCErrorRes(parsedReq.ID, ErrOverRateLimit) + continue + } + id := string(parsedReq.ID) // If this is a duplicate Request ID, move the Request to a new batchGroup ids[id]++ @@ -494,6 +541,14 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context ) } +func (s *Server) isUnlimitedOrigin(origin string) bool { + return s.limExemptOrigins[strings.ToLower(origin)] +} + +func (s *Server) isUnlimitedUserAgent(origin string) bool { + return s.limExemptUserAgents[strings.ToLower(origin)] +} + func setCacheHeader(w http.ResponseWriter, cached bool) { if cached { w.Header().Set(cacheStatusHdr, "HIT") From 320d6ebeea16075fa26c7861c6f0551883caef1d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 15 Sep 2022 08:51:21 +0000 Subject: [PATCH 021/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 6 ++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index ac6a0c3..5593f97 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,11 @@ # @eth-optimism/proxyd +## 3.10.0 + +### Minor Changes + +- 157ccc84: Support per-method rate limiting + ## 3.9.1 ### Patch Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index d078d75..45cfc61 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.9.1", + "version": "3.10.0", "private": true, "dependencies": {} } From 3fb4debfe1f6517d0ac29abd5690822dd5f887c4 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Fri, 16 Sep 2022 19:28:56 +0200 Subject: [PATCH 022/212] proxyd: Add logging for origin and remote IP (#3482) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Mark Tyneway --- proxyd/proxyd/server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index b3ff52e..c65b86e 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -264,6 +264,8 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { "req_id", GetReqID(ctx), "auth", GetAuthCtx(ctx), "user_agent", userAgent, + "origin", origin, + "remote_ip", xff, ) body, err := io.ReadAll(io.LimitReader(r.Body, s.maxBodySize)) From d228a55bd74b48240568b5100e15fbe92786fa57 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Mon, 19 Sep 2022 18:44:08 +0200 Subject: [PATCH 023/212] proxyd: Fix error message in tests (#3503) * proxyd: Fix error message in tests The custom error message wasn't being carried over in batch responses. * review fixes Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- proxyd/proxyd/backend.go | 2 +- .../integration_tests/rate_limit_test.go | 25 ++++++++++--------- .../testdata/frontend_rate_limit.toml | 2 +- proxyd/proxyd/server.go | 16 +++++++++--- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 9a004d0..2cee810 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -75,7 +75,7 @@ var ( } ErrOverRateLimit = &RPCErr{ Code: JSONRPCErrorInternal - 16, - Message: "rate limited", + Message: "over rate limit", HTTPErrorCode: 429, } diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/proxyd/integration_tests/rate_limit_test.go index 5d3163a..e4cc698 100644 --- a/proxyd/proxyd/integration_tests/rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/rate_limit_test.go @@ -16,7 +16,8 @@ type resWithCode struct { res []byte } -const frontendOverLimitResponse = `{"error":{"code":-32016,"message":"over rate limit"},"id":null,"jsonrpc":"2.0"}` +const frontendOverLimitResponse = `{"error":{"code":-32016,"message":"over rate limit with special message"},"id":null,"jsonrpc":"2.0"}` +const frontendOverLimitResponseWithID = `{"error":{"code":-32016,"message":"over rate limit with special message"},"id":999,"jsonrpc":"2.0"}` var ethChainID = "eth_chainId" @@ -31,7 +32,7 @@ func TestBackendMaxRPSLimit(t *testing.T) { shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() - limitedRes, codes := spamReqs(t, client, ethChainID, 503) + limitedRes, codes := spamReqs(t, client, ethChainID, 503, 3) require.Equal(t, 2, codes[200]) require.Equal(t, 1, codes[503]) RequireEqualJSON(t, []byte(noBackendsResponse), limitedRes) @@ -50,7 +51,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { t.Run("non-exempt over limit", func(t *testing.T) { client := NewProxydClient("http://127.0.0.1:8545") - limitedRes, codes := spamReqs(t, client, ethChainID, 429) + limitedRes, codes := spamReqs(t, client, ethChainID, 429, 3) require.Equal(t, 1, codes[429]) require.Equal(t, 2, codes[200]) RequireEqualJSON(t, []byte(frontendOverLimitResponse), limitedRes) @@ -60,7 +61,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h := make(http.Header) h.Set("User-Agent", "exempt_agent") client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - _, codes := spamReqs(t, client, ethChainID, 429) + _, codes := spamReqs(t, client, ethChainID, 429, 3) require.Equal(t, 3, codes[200]) }) @@ -68,7 +69,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h := make(http.Header) h.Set("Origin", "exempt_origin") client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - _, codes := spamReqs(t, client, ethChainID, 429) + _, codes := spamReqs(t, client, ethChainID, 429, 3) require.Equal(t, 3, codes[200]) }) @@ -79,7 +80,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h2.Set("X-Forwarded-For", "1.1.1.1") client1 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h1) client2 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h2) - _, codes := spamReqs(t, client1, ethChainID, 429) + _, codes := spamReqs(t, client1, ethChainID, 429, 3) require.Equal(t, 1, codes[429]) require.Equal(t, 2, codes[200]) _, code, err := client2.SendRPC(ethChainID, nil) @@ -95,11 +96,11 @@ func TestFrontendMaxRPSLimit(t *testing.T) { t.Run("RPC override", func(t *testing.T) { client := NewProxydClient("http://127.0.0.1:8545") - limitedRes, codes := spamReqs(t, client, "eth_foobar", 429) + limitedRes, codes := spamReqs(t, client, "eth_foobar", 429, 2) // use 2 and 1 here since the limit for eth_foobar is 1 - require.Equal(t, 2, codes[429]) + require.Equal(t, 1, codes[429]) require.Equal(t, 1, codes[200]) - RequireEqualJSON(t, []byte(frontendOverLimitResponse), limitedRes) + RequireEqualJSON(t, []byte(frontendOverLimitResponseWithID), limitedRes) }) time.Sleep(time.Second) @@ -140,9 +141,9 @@ func TestFrontendMaxRPSLimit(t *testing.T) { }) } -func spamReqs(t *testing.T, client *ProxydHTTPClient, method string, limCode int) ([]byte, map[int]int) { +func spamReqs(t *testing.T, client *ProxydHTTPClient, method string, limCode int, n int) ([]byte, map[int]int) { resCh := make(chan *resWithCode) - for i := 0; i < 3; i++ { + for i := 0; i < n; i++ { go func() { res, code, err := client.SendRPC(method, nil) require.NoError(t, err) @@ -155,7 +156,7 @@ func spamReqs(t *testing.T, client *ProxydHTTPClient, method string, limCode int codes := make(map[int]int) var limitedRes []byte - for i := 0; i < 3; i++ { + for i := 0; i < n; i++ { res := <-resCh code := res.code if codes[code] == 0 { diff --git a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml index 8615f3f..f34840d 100644 --- a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml @@ -21,7 +21,7 @@ eth_foobar = "main" rate_per_second = 2 exempt_origins = ["exempt_origin"] exempt_user_agents = ["exempt_agent"] -error_message = "over rate limit" +error_message = "over rate limit with special message" [rate_limit.method_overrides.eth_foobar] limit = 1 diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index c65b86e..455989d 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -244,8 +244,11 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { } if isLimited("") { - rpcErr := ErrOverRateLimit.Clone() - rpcErr.Message = s.limConfig.ErrorMessage + rpcErr := ErrOverRateLimit + if s.limConfig.ErrorMessage != "" { + rpcErr = ErrOverRateLimit.Clone() + rpcErr.Message = s.limConfig.ErrorMessage + } RecordRPCError(ctx, BackendProxyd, "unknown", rpcErr) log.Warn( "rate limited request", @@ -391,8 +394,13 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL "req_id", GetReqID(ctx), "method", parsedReq.Method, ) - RecordRPCError(ctx, BackendProxyd, parsedReq.Method, ErrOverRateLimit) - responses[i] = NewRPCErrorRes(parsedReq.ID, ErrOverRateLimit) + rpcErr := ErrOverRateLimit + if s.limConfig.ErrorMessage != "" { + rpcErr = rpcErr.Clone() + rpcErr.Message = s.limConfig.ErrorMessage + } + RecordRPCError(ctx, BackendProxyd, parsedReq.Method, rpcErr) + responses[i] = NewRPCErrorRes(parsedReq.ID, rpcErr) continue } From 70557fdafd8ef00e608b6d79844d1187b7901ae7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Sep 2022 15:21:51 +0000 Subject: [PATCH 024/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 7 +++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 5593f97..552a86e 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,12 @@ # @eth-optimism/proxyd +## 3.10.1 + +### Patch Changes + +- b82a8f48: Add logging for origin and remote IP' +- 1bf9559c: Carry over custom limit message in batches + ## 3.10.0 ### Minor Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 45cfc61..6aea748 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.10.0", + "version": "3.10.1", "private": true, "dependencies": {} } From 485258d3c27f2dcf9ce53dd9ffc2df2c59f66aed Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Fri, 23 Sep 2022 22:21:12 +0200 Subject: [PATCH 025/212] proxyd: Add customizable whitelist error message (#3544) * proxyd: Add customizable whitelist error message Alchemy is asking for this so that we can include their affiliate link. * add missing test case * fix error message * goimports --- proxyd/proxyd/config.go | 25 ++++++++++--------- .../integration_tests/testdata/whitelist.toml | 2 ++ .../proxyd/integration_tests/testdata/ws.toml | 2 ++ .../integration_tests/validation_test.go | 2 +- proxyd/proxyd/integration_tests/ws_test.go | 9 +++++++ proxyd/proxyd/proxyd.go | 11 ++++++++ proxyd/proxyd/server.go | 18 +++---------- 7 files changed, 42 insertions(+), 27 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index ba60d67..466deb9 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -96,18 +96,19 @@ type BackendGroupsConfig map[string]*BackendGroupConfig type MethodMappingsConfig map[string]string type Config struct { - WSBackendGroup string `toml:"ws_backend_group"` - Server ServerConfig `toml:"server"` - Cache CacheConfig `toml:"cache"` - Redis RedisConfig `toml:"redis"` - Metrics MetricsConfig `toml:"metrics"` - RateLimit RateLimitConfig `toml:"rate_limit"` - BackendOptions BackendOptions `toml:"backend"` - Backends BackendsConfig `toml:"backends"` - Authentication map[string]string `toml:"authentication"` - BackendGroups BackendGroupsConfig `toml:"backend_groups"` - RPCMethodMappings map[string]string `toml:"rpc_method_mappings"` - WSMethodWhitelist []string `toml:"ws_method_whitelist"` + WSBackendGroup string `toml:"ws_backend_group"` + Server ServerConfig `toml:"server"` + Cache CacheConfig `toml:"cache"` + Redis RedisConfig `toml:"redis"` + Metrics MetricsConfig `toml:"metrics"` + RateLimit RateLimitConfig `toml:"rate_limit"` + BackendOptions BackendOptions `toml:"backend"` + Backends BackendsConfig `toml:"backends"` + Authentication map[string]string `toml:"authentication"` + BackendGroups BackendGroupsConfig `toml:"backend_groups"` + RPCMethodMappings map[string]string `toml:"rpc_method_mappings"` + WSMethodWhitelist []string `toml:"ws_method_whitelist"` + WhitelistErrorMessage string `toml:"whitelist_error_message"` } func ReadFromEnvOrConfig(value string) (string, error) { diff --git a/proxyd/proxyd/integration_tests/testdata/whitelist.toml b/proxyd/proxyd/integration_tests/testdata/whitelist.toml index 55b118c..4a65248 100644 --- a/proxyd/proxyd/integration_tests/testdata/whitelist.toml +++ b/proxyd/proxyd/integration_tests/testdata/whitelist.toml @@ -1,3 +1,5 @@ +whitelist_error_message = "rpc method is not whitelisted custom message" + [server] rpc_port = 8545 diff --git a/proxyd/proxyd/integration_tests/testdata/ws.toml b/proxyd/proxyd/integration_tests/testdata/ws.toml index f86b22f..4642e6b 100644 --- a/proxyd/proxyd/integration_tests/testdata/ws.toml +++ b/proxyd/proxyd/integration_tests/testdata/ws.toml @@ -1,3 +1,5 @@ +whitelist_error_message = "rpc method is not whitelisted" + ws_backend_group = "main" ws_method_whitelist = [ diff --git a/proxyd/proxyd/integration_tests/validation_test.go b/proxyd/proxyd/integration_tests/validation_test.go index be964c1..6f0653d 100644 --- a/proxyd/proxyd/integration_tests/validation_test.go +++ b/proxyd/proxyd/integration_tests/validation_test.go @@ -10,7 +10,7 @@ import ( ) const ( - notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":999}` + notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted custom message"},"id":999}` parseErrResponse = `{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}` invalidJSONRPCVersionResponse = `{"error":{"code":-32601,"message":"invalid JSON-RPC version"},"id":null,"jsonrpc":"2.0"}` invalidIDResponse = `{"error":{"code":-32601,"message":"invalid ID"},"id":null,"jsonrpc":"2.0"}` diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go index d93d3a7..9ae12ab 100644 --- a/proxyd/proxyd/integration_tests/ws_test.go +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum-optimism/optimism/proxyd" "github.com/gorilla/websocket" "github.com/stretchr/testify/require" @@ -42,6 +44,13 @@ func TestConcurrentWSPanic(t *testing.T) { require.NoError(t, err) defer shutdown() + // suppress tons of log messages + oldHandler := log.Root().GetHandler() + log.Root().SetHandler(log.DiscardHandler()) + defer func() { + log.Root().SetHandler(oldHandler) + }() + <-readyCh var wg sync.WaitGroup diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index e9bbe42..af9f44e 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -55,6 +55,17 @@ func Start(config *Config) (func(), error) { } } + // While modifying shared globals is a bad practice, the alternative + // is to clone these errors on every invocation. This is inefficient. + // We'd also have to make sure that errors.Is and errors.As continue + // to function properly on the cloned errors. + if config.RateLimit.ErrorMessage != "" { + ErrOverRateLimit.Message = config.RateLimit.ErrorMessage + } + if config.WhitelistErrorMessage != "" { + ErrMethodNotWhitelisted.Message = config.WhitelistErrorMessage + } + maxConcurrentRPCs := config.Server.MaxConcurrentRPCs if maxConcurrentRPCs == 0 { maxConcurrentRPCs = math.MaxInt64 diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 455989d..509dc6a 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -244,12 +244,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { } if isLimited("") { - rpcErr := ErrOverRateLimit - if s.limConfig.ErrorMessage != "" { - rpcErr = ErrOverRateLimit.Clone() - rpcErr.Message = s.limConfig.ErrorMessage - } - RecordRPCError(ctx, BackendProxyd, "unknown", rpcErr) + RecordRPCError(ctx, BackendProxyd, "unknown", ErrOverRateLimit) log.Warn( "rate limited request", "req_id", GetReqID(ctx), @@ -258,7 +253,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { "origin", origin, "remote_ip", xff, ) - writeRPCError(ctx, w, nil, rpcErr) + writeRPCError(ctx, w, nil, ErrOverRateLimit) return } @@ -394,13 +389,8 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL "req_id", GetReqID(ctx), "method", parsedReq.Method, ) - rpcErr := ErrOverRateLimit - if s.limConfig.ErrorMessage != "" { - rpcErr = rpcErr.Clone() - rpcErr.Message = s.limConfig.ErrorMessage - } - RecordRPCError(ctx, BackendProxyd, parsedReq.Method, rpcErr) - responses[i] = NewRPCErrorRes(parsedReq.ID, rpcErr) + RecordRPCError(ctx, BackendProxyd, parsedReq.Method, ErrOverRateLimit) + responses[i] = NewRPCErrorRes(parsedReq.ID, ErrOverRateLimit) continue } From 537717610e56dee4f9b553dc19bce797044093b6 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Fri, 23 Sep 2022 23:06:02 +0200 Subject: [PATCH 026/212] proxyd: Add batch size metric and configurable max (#3545) * proxyd: Add batch size metric and configurable max The max batch size will be overwritten if it is over `MaxBatchRPCCallsHardLimit`. Builds on https://github.com/ethereum-optimism/optimism/pull/3544. * changeset * fix lint * fix test --- proxyd/proxyd/config.go | 6 ++ .../proxyd/integration_tests/batching_test.go | 57 ++++++++++++------- .../integration_tests/testdata/batching.toml | 4 ++ proxyd/proxyd/metrics.go | 18 ++++++ proxyd/proxyd/proxyd.go | 4 ++ proxyd/proxyd/server.go | 13 ++++- 6 files changed, 79 insertions(+), 23 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 466deb9..0647074 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -95,6 +95,11 @@ type BackendGroupsConfig map[string]*BackendGroupConfig type MethodMappingsConfig map[string]string +type BatchConfig struct { + MaxSize int `toml:"max_size"` + ErrorMessage string `toml:"error_message"` +} + type Config struct { WSBackendGroup string `toml:"ws_backend_group"` Server ServerConfig `toml:"server"` @@ -104,6 +109,7 @@ type Config struct { RateLimit RateLimitConfig `toml:"rate_limit"` BackendOptions BackendOptions `toml:"backend"` Backends BackendsConfig `toml:"backends"` + BatchConfig BatchConfig `toml:"batch"` Authentication map[string]string `toml:"authentication"` BackendGroups BackendGroupsConfig `toml:"backend_groups"` RPCMethodMappings map[string]string `toml:"rpc_method_mappings"` diff --git a/proxyd/proxyd/integration_tests/batching_test.go b/proxyd/proxyd/integration_tests/batching_test.go index 1bcbba9..b0f811c 100644 --- a/proxyd/proxyd/integration_tests/batching_test.go +++ b/proxyd/proxyd/integration_tests/batching_test.go @@ -33,13 +33,13 @@ func TestBatching(t *testing.T) { callMock1 := mockResult{"eth_call", "1", "ekans1"} tests := []struct { - name string - handler http.Handler - mocks []mockResult - reqs []*proxyd.RPCReq - expectedRes string - maxBatchSize int - numExpectedForwards int + name string + handler http.Handler + mocks []mockResult + reqs []*proxyd.RPCReq + expectedRes string + maxUpstreamBatchSize int + numExpectedForwards int }{ { name: "backend returns batches out of order", @@ -49,9 +49,9 @@ func TestBatching(t *testing.T) { NewRPCReq("2", "eth_chainId", nil), NewRPCReq("3", "eth_chainId", nil), }, - expectedRes: asArray(chainIDResponse1, chainIDResponse2, chainIDResponse3), - maxBatchSize: 2, - numExpectedForwards: 2, + expectedRes: asArray(chainIDResponse1, chainIDResponse2, chainIDResponse3), + maxUpstreamBatchSize: 2, + numExpectedForwards: 2, }, { // infura behavior @@ -65,8 +65,8 @@ func TestBatching(t *testing.T) { `{"error":{"code":-32011,"message":"no backends available for method"},"id":1,"jsonrpc":"2.0"}`, `{"error":{"code":-32011,"message":"no backends available for method"},"id":2,"jsonrpc":"2.0"}`, ), - maxBatchSize: 10, - numExpectedForwards: 1, + maxUpstreamBatchSize: 10, + numExpectedForwards: 1, }, { name: "backend returns single RPC response object for minibatches", @@ -79,8 +79,8 @@ func TestBatching(t *testing.T) { `{"error":{"code":-32011,"message":"no backends available for method"},"id":1,"jsonrpc":"2.0"}`, `{"error":{"code":-32011,"message":"no backends available for method"},"id":2,"jsonrpc":"2.0"}`, ), - maxBatchSize: 1, - numExpectedForwards: 2, + maxUpstreamBatchSize: 1, + numExpectedForwards: 2, }, { name: "duplicate request ids are on distinct batches", @@ -96,9 +96,24 @@ func TestBatching(t *testing.T) { NewRPCReq("1", "eth_chainId", nil), NewRPCReq("1", "eth_call", nil), }, - expectedRes: asArray(netVersionResponse1, chainIDResponse2, chainIDResponse1, callResponse1), - maxBatchSize: 2, - numExpectedForwards: 3, + expectedRes: asArray(netVersionResponse1, chainIDResponse2, chainIDResponse1, callResponse1), + maxUpstreamBatchSize: 2, + numExpectedForwards: 3, + }, + { + name: "over max size", + mocks: []mockResult{}, + reqs: []*proxyd.RPCReq{ + NewRPCReq("1", "net_version", nil), + NewRPCReq("2", "eth_chainId", nil), + NewRPCReq("3", "eth_chainId", nil), + NewRPCReq("4", "eth_call", nil), + NewRPCReq("5", "eth_call", nil), + NewRPCReq("6", "eth_call", nil), + }, + expectedRes: "{\"error\":{\"code\":-32014,\"message\":\"over batch size custom message\"},\"id\":null,\"jsonrpc\":\"2.0\"}", + maxUpstreamBatchSize: 2, + numExpectedForwards: 0, }, { name: "eth_accounts does not get forwarded", @@ -109,15 +124,15 @@ func TestBatching(t *testing.T) { NewRPCReq("1", "eth_call", nil), NewRPCReq("2", "eth_accounts", nil), }, - expectedRes: asArray(callResponse1, ethAccountsResponse2), - maxBatchSize: 2, - numExpectedForwards: 1, + expectedRes: asArray(callResponse1, ethAccountsResponse2), + maxUpstreamBatchSize: 2, + numExpectedForwards: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - config.Server.MaxUpstreamBatchSize = tt.maxBatchSize + config.Server.MaxUpstreamBatchSize = tt.maxUpstreamBatchSize handler := tt.handler if handler == nil { diff --git a/proxyd/proxyd/integration_tests/testdata/batching.toml b/proxyd/proxyd/integration_tests/testdata/batching.toml index 2aa591d..4762835 100644 --- a/proxyd/proxyd/integration_tests/testdata/batching.toml +++ b/proxyd/proxyd/integration_tests/testdata/batching.toml @@ -17,3 +17,7 @@ backends = ["good"] eth_chainId = "main" net_version = "main" eth_call = "main" + +[batch] +error_message = "over batch size custom message" +max_size = 5 \ No newline at end of file diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 5241dfa..a3cfe45 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -222,6 +222,20 @@ var ( }, []string{ "backend_name", }) + + batchSizeHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: MetricsNamespace, + Name: "batch_size_summary", + Help: "Summary of batch sizes", + Buckets: []float64{ + 1, + 5, + 10, + 25, + 50, + 100, + }, + }) ) func RecordRedisError(source string) { @@ -278,3 +292,7 @@ func RecordCacheHit(method string) { func RecordCacheMiss(method string) { cacheMissesTotal.WithLabelValues(method).Inc() } + +func RecordBatchSize(size int) { + batchSizeHistogram.Observe(float64(size)) +} diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index af9f44e..12a6a1a 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -65,6 +65,9 @@ func Start(config *Config) (func(), error) { if config.WhitelistErrorMessage != "" { ErrMethodNotWhitelisted.Message = config.WhitelistErrorMessage } + if config.BatchConfig.ErrorMessage != "" { + ErrTooManyBatchRequests.Message = config.BatchConfig.ErrorMessage + } maxConcurrentRPCs := config.Server.MaxConcurrentRPCs if maxConcurrentRPCs == 0 { @@ -236,6 +239,7 @@ func Start(config *Config) (func(), error) { config.RateLimit, config.Server.EnableRequestLog, config.Server.MaxRequestBodyLogLen, + config.BatchConfig.MaxSize, ) if err != nil { return nil, fmt.Errorf("error creating server: %w", err) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 509dc6a..94787d1 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -28,7 +28,7 @@ const ( ContextKeyAuth = "authorization" ContextKeyReqID = "req_id" ContextKeyXForwardedFor = "x_forwarded_for" - MaxBatchRPCCalls = 100 + MaxBatchRPCCallsHardLimit = 100 cacheStatusHdr = "X-Proxyd-Cache-Status" defaultServerTimeout = time.Second * 10 maxRequestBodyLogLen = 2000 @@ -48,6 +48,7 @@ type Server struct { authenticatedPaths map[string]string timeout time.Duration maxUpstreamBatchSize int + maxBatchSize int upgrader *websocket.Upgrader mainLim limiter.Store overrideLims map[string]limiter.Store @@ -75,6 +76,7 @@ func NewServer( rateLimitConfig RateLimitConfig, enableRequestLog bool, maxRequestBodyLogLen int, + maxBatchSize int, ) (*Server, error) { if cache == nil { cache = &NoopRPCCache{} @@ -92,6 +94,10 @@ func NewServer( maxUpstreamBatchSize = defaultMaxUpstreamBatchSize } + if maxBatchSize == 0 || maxBatchSize > MaxBatchRPCCallsHardLimit { + maxBatchSize = MaxBatchRPCCallsHardLimit + } + var mainLim limiter.Store limExemptOrigins := make(map[string]bool) limExemptUserAgents := make(map[string]bool) @@ -139,6 +145,7 @@ func NewServer( cache: cache, enableRequestLog: enableRequestLog, maxRequestBodyLogLen: maxRequestBodyLogLen, + maxBatchSize: maxBatchSize, upgrader: &websocket.Upgrader{ HandshakeTimeout: 5 * time.Second, }, @@ -291,7 +298,9 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { return } - if len(reqs) > MaxBatchRPCCalls { + RecordBatchSize(len(reqs)) + + if len(reqs) > s.maxBatchSize { RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrTooManyBatchRequests) writeRPCError(ctx, w, nil, ErrTooManyBatchRequests) return From 09552fddeaea4faad07939ff12cc92ddf1c8cc68 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 29 Sep 2022 01:37:00 +0000 Subject: [PATCH 027/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 7 +++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 552a86e..64a0820 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,12 @@ # @eth-optimism/proxyd +## 3.10.2 + +### Patch Changes + +- 6bb35fd8: Add customizable whitelist error +- 7121648c: Batch metrics and max batch size + ## 3.10.1 ### Patch Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 6aea748..b18a19d 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.10.1", + "version": "3.10.2", "private": true, "dependencies": {} } From 15a59bad99d2561b9d0ff3b628e3b0ddbe65eed1 Mon Sep 17 00:00:00 2001 From: Chris Wessels Date: Tue, 11 Oct 2022 07:51:02 -0700 Subject: [PATCH 028/212] fix(proxyd): Fix compliance with JSON-RPC 2.0 spec by adding optional RPCError.Data (#3683) * fix: add optional data field to RPCError struct * fix: formatting lint * feat(proxyd): add changeset --- proxyd/proxyd/rpc.go | 1 + proxyd/proxyd/rpc_test.go | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/rpc.go b/proxyd/proxyd/rpc.go index e34b010..0c30d64 100644 --- a/proxyd/proxyd/rpc.go +++ b/proxyd/proxyd/rpc.go @@ -57,6 +57,7 @@ func (r *RPCRes) MarshalJSON() ([]byte, error) { type RPCErr struct { Code int `json:"code"` Message string `json:"message"` + Data string `json:"data,omitempty"` HTTPErrorCode int `json:"-"` } diff --git a/proxyd/proxyd/rpc_test.go b/proxyd/proxyd/rpc_test.go index 0d38dec..e30fe93 100644 --- a/proxyd/proxyd/rpc_test.go +++ b/proxyd/proxyd/rpc_test.go @@ -45,7 +45,7 @@ func TestRPCResJSON(t *testing.T) { `{"jsonrpc":"2.0","result":null,"id":123}`, }, { - "error result", + "error result without data", &RPCRes{ JSONRPC: JSONRPCVersion, Error: &RPCErr{ @@ -56,6 +56,19 @@ func TestRPCResJSON(t *testing.T) { }, `{"jsonrpc":"2.0","error":{"code":1234,"message":"test err"},"id":123}`, }, + { + "error result with data", + &RPCRes{ + JSONRPC: JSONRPCVersion, + Error: &RPCErr{ + Code: 1234, + Message: "test err", + Data: "revert", + }, + ID: []byte("123"), + }, + `{"jsonrpc":"2.0","error":{"code":1234,"message":"test err","data":"revert"},"id":123}`, + }, { "string ID", &RPCRes{ From fa7425683a1715f68c9c2349df5f36b57201c30b Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sun, 9 Oct 2022 14:20:29 -0500 Subject: [PATCH 029/212] proxyd: Custom rate limiter implementation Our current proxyd deployment does not share rate limit state across multiple servers within a backend group. This means that rate limits on the public endpoint are artifically high. This PR adds a Redis-based rate limiter to fix this problem. While our current rate limiting library (github.com/sethvargo/go-limiter) _does_ support Redis, the client library it uses is not type safe, is less performant, and would require us to update the other places we use Redis. To avoid these issues, I created a simple rate limiting interface with both Redis and memory backend. Note that this PR only adds the new implementations - it does not integrate them with the rest of the codebase. I'll do that in a separate PR to make review easier. --- ...ate_limiter.go => backend_rate_limiter.go} | 0 proxyd/proxyd/config.go | 1 + proxyd/proxyd/frontend_rate_limiter.go | 121 ++++++++++++++++++ proxyd/proxyd/frontend_rate_limiter_test.go | 53 ++++++++ 4 files changed, 175 insertions(+) rename proxyd/proxyd/{rate_limiter.go => backend_rate_limiter.go} (100%) create mode 100644 proxyd/proxyd/frontend_rate_limiter.go create mode 100644 proxyd/proxyd/frontend_rate_limiter_test.go diff --git a/proxyd/proxyd/rate_limiter.go b/proxyd/proxyd/backend_rate_limiter.go similarity index 100% rename from proxyd/proxyd/rate_limiter.go rename to proxyd/proxyd/backend_rate_limiter.go diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 0647074..aea54a9 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -41,6 +41,7 @@ type MetricsConfig struct { } type RateLimitConfig struct { + UseRedis bool `toml:"use_redis"` RatePerSecond int `toml:"rate_per_second"` ExemptOrigins []string `toml:"exempt_origins"` ExemptUserAgents []string `toml:"exempt_user_agents"` diff --git a/proxyd/proxyd/frontend_rate_limiter.go b/proxyd/proxyd/frontend_rate_limiter.go new file mode 100644 index 0000000..3b06052 --- /dev/null +++ b/proxyd/proxyd/frontend_rate_limiter.go @@ -0,0 +1,121 @@ +package proxyd + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/go-redis/redis/v8" +) + +type FrontendRateLimiter interface { + // Take consumes a key, and a maximum number of requests + // per time interval. It returns a boolean denoting if + // the limit could be taken, or an error if a failure + // occurred in the backing rate limit implementation. + // + // No error will be returned if the limit could not be taken + // as a result of the requestor being over the limit. + Take(ctx context.Context, key string, max int) (bool, error) +} + +// limitedKeys is a wrapper around a map that stores a truncated +// timestamp and a mutex. The map is used to keep track of rate +// limit keys, and their used limits. +type limitedKeys struct { + truncTS int64 + keys map[string]int + mtx sync.Mutex +} + +func newLimitedKeys(t int64) *limitedKeys { + return &limitedKeys{ + truncTS: t, + keys: make(map[string]int), + } +} + +func (l *limitedKeys) Take(key string, max int) bool { + l.mtx.Lock() + defer l.mtx.Unlock() + val, ok := l.keys[key] + if !ok { + l.keys[key] = 0 + val = 0 + } + l.keys[key] = val + 1 + return val < max +} + +// MemoryFrontendRateLimiter is a rate limiter that stores +// all rate limiting information in local memory. It works +// by storing a limitedKeys struct that references the +// truncated timestamp at which the struct was created. If +// the current truncated timestamp doesn't match what's +// referenced, the limit is reset. Otherwise, values in +// a map are incremented to represent the limit. +type MemoryFrontendRateLimiter struct { + currGeneration *limitedKeys + dur time.Duration + mtx sync.Mutex +} + +func NewMemoryFrontendRateLimit(dur time.Duration) FrontendRateLimiter { + return &MemoryFrontendRateLimiter{ + dur: dur, + } +} + +func (m *MemoryFrontendRateLimiter) Take(ctx context.Context, key string, max int) (bool, error) { + m.mtx.Lock() + // Create truncated timestamp + truncTS := truncateNow(m.dur) + + // If there is no current rate limit map or the rate limit map reference + // a different timestamp, reset limits. + if m.currGeneration == nil || m.currGeneration.truncTS != truncTS { + m.currGeneration = newLimitedKeys(truncTS) + } + + // Pull out the limiter so we can unlock before incrementing the limit. + limiter := m.currGeneration + + m.mtx.Unlock() + + return limiter.Take(key, max), nil +} + +// RedisFrontendRateLimiter is a rate limiter that stores data in Redis. +// It uses the basic rate limiter pattern described on the Redis best +// practices website: https://redis.com/redis-best-practices/basic-rate-limiting/. +type RedisFrontendRateLimiter struct { + r *redis.Client + dur time.Duration +} + +func NewRedisFrontendRateLimiter(r *redis.Client, dur time.Duration) FrontendRateLimiter { + return &RedisFrontendRateLimiter{r: r, dur: dur} +} + +func (r *RedisFrontendRateLimiter) Take(ctx context.Context, key string, max int) (bool, error) { + var incr *redis.IntCmd + truncTS := truncateNow(r.dur) + fullKey := fmt.Sprintf("%s:%d", key, truncTS) + _, err := r.r.Pipelined(ctx, func(pipe redis.Pipeliner) error { + incr = pipe.Incr(ctx, fullKey) + pipe.Expire(ctx, fullKey, r.dur-time.Second) + return nil + }) + if err != nil { + return false, err + } + + return incr.Val()-1 < int64(max), nil +} + +// truncateNow truncates the current timestamp +// to the specified duration. +func truncateNow(dur time.Duration) int64 { + return time.Now().Truncate(dur).Unix() +} diff --git a/proxyd/proxyd/frontend_rate_limiter_test.go b/proxyd/proxyd/frontend_rate_limiter_test.go new file mode 100644 index 0000000..c3d43cd --- /dev/null +++ b/proxyd/proxyd/frontend_rate_limiter_test.go @@ -0,0 +1,53 @@ +package proxyd + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/alicebob/miniredis" + "github.com/go-redis/redis/v8" + "github.com/stretchr/testify/require" +) + +func TestFrontendRateLimiter(t *testing.T) { + redisServer, err := miniredis.Run() + require.NoError(t, err) + defer redisServer.Close() + + redisClient := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("127.0.0.1:%s", redisServer.Port()), + }) + + lims := []struct { + name string + frl FrontendRateLimiter + }{ + {"memory", NewMemoryFrontendRateLimit(2 * time.Second)}, + {"redis", NewRedisFrontendRateLimiter(redisClient, 2*time.Second)}, + } + + max := 2 + for _, cfg := range lims { + frl := cfg.frl + ctx := context.Background() + t.Run(cfg.name, func(t *testing.T) { + for i := 0; i < 4; i++ { + ok, err := frl.Take(ctx, "foo", max) + require.NoError(t, err) + require.Equal(t, i < max, ok) + ok, err = frl.Take(ctx, "bar", max) + require.NoError(t, err) + require.Equal(t, i < max, ok) + } + time.Sleep(2 * time.Second) + for i := 0; i < 4; i++ { + ok, _ := frl.Take(ctx, "foo", max) + require.Equal(t, i < max, ok) + ok, _ = frl.Take(ctx, "bar", max) + require.Equal(t, i < max, ok) + } + }) + } +} From f737002baac3dc134e4667e418b9daba8b980696 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sun, 9 Oct 2022 15:26:27 -0500 Subject: [PATCH 030/212] proxyd: Integrate custom rate limiter Integrates the custom rate limiter in the previous PR into the rest of the application. Also takes the opportunity to clean up how we instantiate Redis clients so that we can share them among multiple different services. There are some config changes in this PR. Specifically, you must specify a `base_rate` and `base_interval` in the rate limit config. --- proxyd/proxyd/backend_rate_limiter.go | 12 +---- proxyd/proxyd/cache.go | 12 +---- proxyd/proxyd/config.go | 3 +- proxyd/proxyd/frontend_rate_limiter.go | 42 ++++++++++----- proxyd/proxyd/frontend_rate_limiter_test.go | 14 ++--- .../proxyd/integration_tests/failover_test.go | 4 +- .../testdata/frontend_rate_limit.toml | 3 +- proxyd/proxyd/metrics.go | 6 +++ proxyd/proxyd/proxyd.go | 28 +++++----- proxyd/proxyd/redis.go | 22 ++++++++ proxyd/proxyd/server.go | 51 +++++++++---------- 11 files changed, 116 insertions(+), 81 deletions(-) create mode 100644 proxyd/proxyd/redis.go diff --git a/proxyd/proxyd/backend_rate_limiter.go b/proxyd/proxyd/backend_rate_limiter.go index fe286e6..03c6436 100644 --- a/proxyd/proxyd/backend_rate_limiter.go +++ b/proxyd/proxyd/backend_rate_limiter.go @@ -57,22 +57,14 @@ type RedisBackendRateLimiter struct { tkMtx sync.Mutex } -func NewRedisRateLimiter(url string) (BackendRateLimiter, error) { - opts, err := redis.ParseURL(url) - if err != nil { - return nil, err - } - rdb := redis.NewClient(opts) - if err := rdb.Ping(context.Background()).Err(); err != nil { - return nil, wrapErr(err, "error connecting to redis") - } +func NewRedisRateLimiter(rdb *redis.Client) BackendRateLimiter { out := &RedisBackendRateLimiter{ rdb: rdb, randID: randStr(20), touchKeys: make(map[string]time.Duration), } go out.touch() - return out, nil + return out } func (r *RedisBackendRateLimiter) IsBackendOnline(name string) (bool, error) { diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 69dbb0b..73b7fd8 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -46,16 +46,8 @@ type redisCache struct { rdb *redis.Client } -func newRedisCache(url string) (*redisCache, error) { - opts, err := redis.ParseURL(url) - if err != nil { - return nil, err - } - rdb := redis.NewClient(opts) - if err := rdb.Ping(context.Background()).Err(); err != nil { - return nil, wrapErr(err, "error connecting to redis") - } - return &redisCache{rdb}, nil +func newRedisCache(rdb *redis.Client) *redisCache { + return &redisCache{rdb} } func (c *redisCache) Get(ctx context.Context, key string) (string, error) { diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index aea54a9..d0a32d6 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -42,7 +42,8 @@ type MetricsConfig struct { type RateLimitConfig struct { UseRedis bool `toml:"use_redis"` - RatePerSecond int `toml:"rate_per_second"` + BaseRate int `toml:"base_rate"` + BaseInterval TOMLDuration `toml:"base_interval"` ExemptOrigins []string `toml:"exempt_origins"` ExemptUserAgents []string `toml:"exempt_user_agents"` ErrorMessage string `toml:"error_message"` diff --git a/proxyd/proxyd/frontend_rate_limiter.go b/proxyd/proxyd/frontend_rate_limiter.go index 3b06052..d377370 100644 --- a/proxyd/proxyd/frontend_rate_limiter.go +++ b/proxyd/proxyd/frontend_rate_limiter.go @@ -17,7 +17,7 @@ type FrontendRateLimiter interface { // // No error will be returned if the limit could not be taken // as a result of the requestor being over the limit. - Take(ctx context.Context, key string, max int) (bool, error) + Take(ctx context.Context, key string) (bool, error) } // limitedKeys is a wrapper around a map that stores a truncated @@ -58,16 +58,18 @@ func (l *limitedKeys) Take(key string, max int) bool { type MemoryFrontendRateLimiter struct { currGeneration *limitedKeys dur time.Duration + max int mtx sync.Mutex } -func NewMemoryFrontendRateLimit(dur time.Duration) FrontendRateLimiter { +func NewMemoryFrontendRateLimit(dur time.Duration, max int) FrontendRateLimiter { return &MemoryFrontendRateLimiter{ dur: dur, + max: max, } } -func (m *MemoryFrontendRateLimiter) Take(ctx context.Context, key string, max int) (bool, error) { +func (m *MemoryFrontendRateLimiter) Take(ctx context.Context, key string) (bool, error) { m.mtx.Lock() // Create truncated timestamp truncTS := truncateNow(m.dur) @@ -83,35 +85,51 @@ func (m *MemoryFrontendRateLimiter) Take(ctx context.Context, key string, max in m.mtx.Unlock() - return limiter.Take(key, max), nil + return limiter.Take(key, m.max), nil } // RedisFrontendRateLimiter is a rate limiter that stores data in Redis. // It uses the basic rate limiter pattern described on the Redis best // practices website: https://redis.com/redis-best-practices/basic-rate-limiting/. type RedisFrontendRateLimiter struct { - r *redis.Client - dur time.Duration + r *redis.Client + dur time.Duration + max int + prefix string } -func NewRedisFrontendRateLimiter(r *redis.Client, dur time.Duration) FrontendRateLimiter { - return &RedisFrontendRateLimiter{r: r, dur: dur} +func NewRedisFrontendRateLimiter(r *redis.Client, dur time.Duration, max int, prefix string) FrontendRateLimiter { + return &RedisFrontendRateLimiter{ + r: r, + dur: dur, + max: max, + prefix: prefix, + } } -func (r *RedisFrontendRateLimiter) Take(ctx context.Context, key string, max int) (bool, error) { +func (r *RedisFrontendRateLimiter) Take(ctx context.Context, key string) (bool, error) { var incr *redis.IntCmd truncTS := truncateNow(r.dur) - fullKey := fmt.Sprintf("%s:%d", key, truncTS) + fullKey := fmt.Sprintf("rate_limit:%s:%s:%d", r.prefix, key, truncTS) _, err := r.r.Pipelined(ctx, func(pipe redis.Pipeliner) error { incr = pipe.Incr(ctx, fullKey) - pipe.Expire(ctx, fullKey, r.dur-time.Second) + pipe.PExpire(ctx, fullKey, r.dur-time.Millisecond) return nil }) if err != nil { + frontendRateLimitTakeErrors.Inc() return false, err } - return incr.Val()-1 < int64(max), nil + return incr.Val()-1 < int64(r.max), nil +} + +type noopFrontendRateLimiter struct{} + +var NoopFrontendRateLimiter = &noopFrontendRateLimiter{} + +func (n *noopFrontendRateLimiter) Take(ctx context.Context, key string) (bool, error) { + return true, nil } // truncateNow truncates the current timestamp diff --git a/proxyd/proxyd/frontend_rate_limiter_test.go b/proxyd/proxyd/frontend_rate_limiter_test.go index c3d43cd..f3542cf 100644 --- a/proxyd/proxyd/frontend_rate_limiter_test.go +++ b/proxyd/proxyd/frontend_rate_limiter_test.go @@ -20,32 +20,32 @@ func TestFrontendRateLimiter(t *testing.T) { Addr: fmt.Sprintf("127.0.0.1:%s", redisServer.Port()), }) + max := 2 lims := []struct { name string frl FrontendRateLimiter }{ - {"memory", NewMemoryFrontendRateLimit(2 * time.Second)}, - {"redis", NewRedisFrontendRateLimiter(redisClient, 2*time.Second)}, + {"memory", NewMemoryFrontendRateLimit(2*time.Second, max)}, + {"redis", NewRedisFrontendRateLimiter(redisClient, 2*time.Second, max, "")}, } - max := 2 for _, cfg := range lims { frl := cfg.frl ctx := context.Background() t.Run(cfg.name, func(t *testing.T) { for i := 0; i < 4; i++ { - ok, err := frl.Take(ctx, "foo", max) + ok, err := frl.Take(ctx, "foo") require.NoError(t, err) require.Equal(t, i < max, ok) - ok, err = frl.Take(ctx, "bar", max) + ok, err = frl.Take(ctx, "bar") require.NoError(t, err) require.Equal(t, i < max, ok) } time.Sleep(2 * time.Second) for i := 0; i < 4; i++ { - ok, _ := frl.Take(ctx, "foo", max) + ok, _ := frl.Take(ctx, "foo") require.Equal(t, i < max, ok) - ok, _ = frl.Take(ctx, "bar", max) + ok, _ = frl.Take(ctx, "bar") require.Equal(t, i < max, ok) } }) diff --git a/proxyd/proxyd/integration_tests/failover_test.go b/proxyd/proxyd/integration_tests/failover_test.go index f80f47c..47c9e26 100644 --- a/proxyd/proxyd/integration_tests/failover_test.go +++ b/proxyd/proxyd/integration_tests/failover_test.go @@ -261,6 +261,8 @@ func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { config.BackendOptions.MaxRetries = 2 // Setup redis to detect offline backends config.Redis.URL = fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()) + redisClient, err := proxyd.NewRedisClient(config.Redis.URL) + require.NoError(t, err) goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse, goodResponse)) defer goodBackend.Close() @@ -285,7 +287,7 @@ func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { require.Equal(t, 1, len(badBackend.Requests())) require.Equal(t, 1, len(goodBackend.Requests())) - rr, err := proxyd.NewRedisRateLimiter(config.Redis.URL) + rr := proxyd.NewRedisRateLimiter(redisClient) require.NoError(t, err) online, err := rr.IsBackendOnline("bad") require.NoError(t, err) diff --git a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml index f34840d..affb855 100644 --- a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml @@ -18,7 +18,8 @@ eth_chainId = "main" eth_foobar = "main" [rate_limit] -rate_per_second = 2 +base_rate = 2 +base_interval = "1s" exempt_origins = ["exempt_origin"] exempt_user_agents = ["exempt_agent"] error_message = "over rate limit with special message" diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index a3cfe45..06fef15 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -236,6 +236,12 @@ var ( 100, }, }) + + frontendRateLimitTakeErrors = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "rate_limit_take_errors", + Help: "Count of errors taking frontend rate limits", + }) ) func RecordRedisError(source string) { diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 12a6a1a..5685633 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/go-redis/redis/v8" "github.com/prometheus/client_golang/prometheus/promhttp" "golang.org/x/sync/semaphore" ) @@ -34,25 +35,29 @@ func Start(config *Config) (func(), error) { } } - var redisURL string + var redisClient *redis.Client if config.Redis.URL != "" { rURL, err := ReadFromEnvOrConfig(config.Redis.URL) if err != nil { return nil, err } - redisURL = rURL + redisClient, err = NewRedisClient(rURL) + if err != nil { + return nil, err + } + } + + if redisClient == nil && config.RateLimit.UseRedis { + return nil, errors.New("must specify a Redis URL if UseRedis is true in rate limit config") } var lim BackendRateLimiter var err error - if redisURL == "" { + if redisClient == nil { log.Warn("redis is not configured, using local rate limiter") lim = NewLocalBackendRateLimiter() } else { - lim, err = NewRedisRateLimiter(redisURL) - if err != nil { - return nil, err - } + lim = NewRedisRateLimiter(redisClient) } // While modifying shared globals is a bad practice, the alternative @@ -206,13 +211,11 @@ func Start(config *Config) (func(), error) { return nil, err } - if redisURL != "" { - if cache, err = newRedisCache(redisURL); err != nil { - return nil, err - } - } else { + if redisClient == nil { log.Warn("redis is not configured, using in-memory cache") cache = newMemoryCache() + } else { + cache = newRedisCache(redisClient) } // Ideally, the BlocKSyncRPCURL should be the sequencer or a HA replica that's not far behind ethClient, err := ethclient.Dial(blockSyncRPCURL) @@ -240,6 +243,7 @@ func Start(config *Config) (func(), error) { config.Server.EnableRequestLog, config.Server.MaxRequestBodyLogLen, config.BatchConfig.MaxSize, + redisClient, ) if err != nil { return nil, fmt.Errorf("error creating server: %w", err) diff --git a/proxyd/proxyd/redis.go b/proxyd/proxyd/redis.go new file mode 100644 index 0000000..e32bff2 --- /dev/null +++ b/proxyd/proxyd/redis.go @@ -0,0 +1,22 @@ +package proxyd + +import ( + "context" + "time" + + "github.com/go-redis/redis/v8" +) + +func NewRedisClient(url string) (*redis.Client, error) { + opts, err := redis.ParseURL(url) + if err != nil { + return nil, err + } + client := redis.NewClient(opts) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := client.Ping(ctx).Err(); err != nil { + return nil, wrapErr(err, "error connecting to redis") + } + return client, nil +} diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 94787d1..e86ba4c 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -13,11 +13,8 @@ import ( "sync" "time" - "github.com/sethvargo/go-limiter" - "github.com/sethvargo/go-limiter/memorystore" - "github.com/sethvargo/go-limiter/noopstore" - "github.com/ethereum/go-ethereum/log" + "github.com/go-redis/redis/v8" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus" @@ -50,9 +47,8 @@ type Server struct { maxUpstreamBatchSize int maxBatchSize int upgrader *websocket.Upgrader - mainLim limiter.Store - overrideLims map[string]limiter.Store - limConfig RateLimitConfig + mainLim FrontendRateLimiter + overrideLims map[string]FrontendRateLimiter limExemptOrigins map[string]bool limExemptUserAgents map[string]bool rpcServer *http.Server @@ -77,6 +73,7 @@ func NewServer( enableRequestLog bool, maxRequestBodyLogLen int, maxBatchSize int, + redisClient *redis.Client, ) (*Server, error) { if cache == nil { cache = &NoopRPCCache{} @@ -98,19 +95,19 @@ func NewServer( maxBatchSize = MaxBatchRPCCallsHardLimit } - var mainLim limiter.Store - limExemptOrigins := make(map[string]bool) - limExemptUserAgents := make(map[string]bool) - if rateLimitConfig.RatePerSecond > 0 { - var err error - mainLim, err = memorystore.New(&memorystore.Config{ - Tokens: uint64(rateLimitConfig.RatePerSecond), - Interval: time.Second, - }) - if err != nil { - return nil, err + limiterFactory := func(dur time.Duration, max int, prefix string) FrontendRateLimiter { + if rateLimitConfig.UseRedis { + return NewRedisFrontendRateLimiter(redisClient, dur, max, prefix) } + return NewMemoryFrontendRateLimit(dur, max) + } + + var mainLim FrontendRateLimiter + limExemptOrigins := make(map[string]bool) + limExemptUserAgents := make(map[string]bool) + if rateLimitConfig.BaseRate > 0 { + mainLim = limiterFactory(time.Duration(rateLimitConfig.BaseInterval), rateLimitConfig.BaseRate, "main") for _, origin := range rateLimitConfig.ExemptOrigins { limExemptOrigins[strings.ToLower(origin)] = true } @@ -118,16 +115,13 @@ func NewServer( limExemptUserAgents[strings.ToLower(agent)] = true } } else { - mainLim, _ = noopstore.New() + mainLim = NoopFrontendRateLimiter } - overrideLims := make(map[string]limiter.Store) + overrideLims := make(map[string]FrontendRateLimiter) for method, override := range rateLimitConfig.MethodOverrides { var err error - overrideLims[method], err = memorystore.New(&memorystore.Config{ - Tokens: uint64(override.Limit), - Interval: time.Duration(override.Interval), - }) + overrideLims[method] = limiterFactory(time.Duration(override.Interval), override.Limit, method) if err != nil { return nil, err } @@ -151,7 +145,6 @@ func NewServer( }, mainLim: mainLim, overrideLims: overrideLims, - limConfig: rateLimitConfig, limExemptOrigins: limExemptOrigins, limExemptUserAgents: limExemptUserAgents, }, nil @@ -235,7 +228,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { return false } - var lim limiter.Store + var lim FrontendRateLimiter if method == "" { lim = s.mainLim } else { @@ -246,7 +239,11 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { return false } - _, _, _, ok, _ := lim.Take(ctx, xff) + ok, err := lim.Take(ctx, xff) + if err != nil { + log.Warn("error taking rate limit", "err", err) + return true + } return !ok } From 053ce4ddfdc87182137006996cd9acd50effba0b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 13 Oct 2022 15:32:03 +0000 Subject: [PATCH 031/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 7 +++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 64a0820..80b7fde 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,12 @@ # @eth-optimism/proxyd +## 3.11.0 + +### Minor Changes + +- b3c5eeec: Fixed JSON-RPC 2.0 specification compliance by adding the optional data field on an RPCError +- 01ae6625: Adds new Redis rate limiter + ## 3.10.2 ### Patch Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index b18a19d..8ee8546 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.10.2", + "version": "3.11.0", "private": true, "dependencies": {} } From 8a624bb2ca2ef2ce56c3b57f3253fc9b5f6da6ac Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Thu, 13 Oct 2022 14:54:30 -0500 Subject: [PATCH 032/212] proxyd: Allow disabling backend rate limiting The backend rate limiter is in place to protect upstreams like the sequencer. However, in many cases it isn't needed and it causes unnecessary requests to Redis. This PR allows this to be disabled, and disables this by default. --- proxyd/proxyd/backend_rate_limiter.go | 28 ++++++++++++++++++- proxyd/proxyd/config.go | 15 +++++----- .../testdata/backend_rate_limit.toml | 5 +++- proxyd/proxyd/proxyd.go | 12 +++++--- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/proxyd/proxyd/backend_rate_limiter.go b/proxyd/proxyd/backend_rate_limiter.go index 03c6436..3cc6fae 100644 --- a/proxyd/proxyd/backend_rate_limiter.go +++ b/proxyd/proxyd/backend_rate_limiter.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "encoding/hex" "fmt" + "math" "sync" "time" @@ -256,5 +257,30 @@ func randStr(l int) string { return hex.EncodeToString(b) } -type ServerRateLimiter struct { +type NoopBackendRateLimiter struct{} + +var noopBackendRateLimiter = &NoopBackendRateLimiter{} + +func (n *NoopBackendRateLimiter) IsBackendOnline(name string) (bool, error) { + return true, nil +} + +func (n *NoopBackendRateLimiter) SetBackendOffline(name string, duration time.Duration) error { + return nil +} + +func (n *NoopBackendRateLimiter) IncBackendRPS(name string) (int, error) { + return math.MaxInt, nil +} + +func (n *NoopBackendRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { + return true, nil +} + +func (n *NoopBackendRateLimiter) DecBackendWSConns(name string) error { + return nil +} + +func (n *NoopBackendRateLimiter) FlushBackendWSConns(names []string) error { + return nil } diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index d0a32d6..de221d9 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -41,13 +41,14 @@ type MetricsConfig struct { } type RateLimitConfig struct { - UseRedis bool `toml:"use_redis"` - BaseRate int `toml:"base_rate"` - BaseInterval TOMLDuration `toml:"base_interval"` - ExemptOrigins []string `toml:"exempt_origins"` - ExemptUserAgents []string `toml:"exempt_user_agents"` - ErrorMessage string `toml:"error_message"` - MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` + UseRedis bool `toml:"use_redis"` + EnableBackendRateLimiter bool `toml:"enable_backend_rate_limiter"` + BaseRate int `toml:"base_rate"` + BaseInterval TOMLDuration `toml:"base_interval"` + ExemptOrigins []string `toml:"exempt_origins"` + ExemptUserAgents []string `toml:"exempt_user_agents"` + ErrorMessage string `toml:"error_message"` + MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` } type RateLimitMethodOverride struct { diff --git a/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml index eca6580..e29bb19 100644 --- a/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml @@ -15,4 +15,7 @@ max_rps = 2 backends = ["good"] [rpc_method_mappings] -eth_chainId = "main" \ No newline at end of file +eth_chainId = "main" + +[rate_limit] +enable_backend_limiter = true \ No newline at end of file diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 5685633..3e21d71 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -53,11 +53,15 @@ func Start(config *Config) (func(), error) { var lim BackendRateLimiter var err error - if redisClient == nil { - log.Warn("redis is not configured, using local rate limiter") - lim = NewLocalBackendRateLimiter() + if config.RateLimit.EnableBackendRateLimiter { + if redisClient != nil { + lim = NewRedisRateLimiter(redisClient) + } else { + log.Warn("redis is not configured, using local rate limiter") + lim = NewLocalBackendRateLimiter() + } } else { - lim = NewRedisRateLimiter(redisClient) + lim = noopBackendRateLimiter } // While modifying shared globals is a bad practice, the alternative From 24a3668bc8d3657ed239f6005b553d7cd183e7d4 Mon Sep 17 00:00:00 2001 From: Zach Howard Date: Fri, 14 Oct 2022 14:56:27 -0400 Subject: [PATCH 033/212] adds log level conf to proxyd (#3704) --- proxyd/proxyd/cmd/proxyd/main.go | 15 +++++++++++++++ proxyd/proxyd/config.go | 1 + proxyd/proxyd/example.config.toml | 2 ++ 3 files changed, 18 insertions(+) diff --git a/proxyd/proxyd/cmd/proxyd/main.go b/proxyd/proxyd/cmd/proxyd/main.go index db3828b..c184a1d 100644 --- a/proxyd/proxyd/cmd/proxyd/main.go +++ b/proxyd/proxyd/cmd/proxyd/main.go @@ -37,6 +37,21 @@ func main() { log.Crit("error reading config file", "err", err) } + // update log level from config + logLevel, err := log.LvlFromString(config.Server.LogLevel) + if err != nil { + logLevel = log.LvlInfo + if config.Server.LogLevel != "" { + log.Warn("invalid server.log_level set: " + config.Server.LogLevel) + } + } + log.Root().SetHandler( + log.LvlFilterHandler( + logLevel, + log.StreamHandler(os.Stdout, log.JSONFormat()), + ), + ) + shutdown, err := proxyd.Start(config) if err != nil { log.Crit("error starting proxyd", "err", err) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index d0a32d6..9890121 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -14,6 +14,7 @@ type ServerConfig struct { WSPort int `toml:"ws_port"` MaxBodySizeBytes int64 `toml:"max_body_size_bytes"` MaxConcurrentRPCs int64 `toml:"max_concurrent_rpcs"` + LogLevel string `toml:"log_level"` // TimeoutSeconds specifies the maximum time spent serving an HTTP request. Note that isn't used for websocket connections TimeoutSeconds int `toml:"timeout_seconds"` diff --git a/proxyd/proxyd/example.config.toml b/proxyd/proxyd/example.config.toml index 2573910..fb8fea9 100644 --- a/proxyd/proxyd/example.config.toml +++ b/proxyd/proxyd/example.config.toml @@ -19,6 +19,8 @@ ws_port = 8085 # Maximum client body size, in bytes, that the server will accept. max_body_size_bytes = 10485760 max_concurrent_rpcs = 1000 +# Server log level +log_level = "info" [redis] # URL to a Redis instance. From fd5e974bd562fe34a96109c7d15b8c7be3f4c99c Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sat, 15 Oct 2022 09:59:11 -0500 Subject: [PATCH 034/212] fix tests --- .../integration_tests/testdata/backend_rate_limit.toml | 2 +- .../integration_tests/testdata/out_of_service_interval.toml | 5 ++++- proxyd/proxyd/integration_tests/testdata/ws.toml | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml index e29bb19..17500f3 100644 --- a/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml @@ -18,4 +18,4 @@ backends = ["good"] eth_chainId = "main" [rate_limit] -enable_backend_limiter = true \ No newline at end of file +enable_backend_rate_limiter = true \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml b/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml index 6663721..4611251 100644 --- a/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml +++ b/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml @@ -19,4 +19,7 @@ ws_url = "$BAD_BACKEND_RPC_URL" backends = ["bad", "good"] [rpc_method_mappings] -eth_chainId = "main" \ No newline at end of file +eth_chainId = "main" + +[rate_limit] +enable_backend_rate_limiter = true \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/testdata/ws.toml b/proxyd/proxyd/integration_tests/testdata/ws.toml index 4642e6b..7340717 100644 --- a/proxyd/proxyd/integration_tests/testdata/ws.toml +++ b/proxyd/proxyd/integration_tests/testdata/ws.toml @@ -26,3 +26,6 @@ backends = ["good"] [rpc_method_mappings] eth_chainId = "main" + +[rate_limit] +enable_backend_rate_limiter = true \ No newline at end of file From fe8a89e61dcdfc05b7b84b135a45d232f30dad48 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Mon, 17 Oct 2022 07:08:33 -0600 Subject: [PATCH 035/212] proxyd: Support pattern matching in origin and user agent Adds support for pattern matchin in exempt origins. This should help with some of the issues Synthetix and others are seeing. --- proxyd/proxyd/server.go | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index e86ba4c..1ee10fa 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -8,6 +8,7 @@ import ( "io" "math" "net/http" + "regexp" "strconv" "strings" "sync" @@ -49,8 +50,8 @@ type Server struct { upgrader *websocket.Upgrader mainLim FrontendRateLimiter overrideLims map[string]FrontendRateLimiter - limExemptOrigins map[string]bool - limExemptUserAgents map[string]bool + limExemptOrigins []*regexp.Regexp + limExemptUserAgents []*regexp.Regexp rpcServer *http.Server wsServer *http.Server cache RPCCache @@ -104,15 +105,23 @@ func NewServer( } var mainLim FrontendRateLimiter - limExemptOrigins := make(map[string]bool) - limExemptUserAgents := make(map[string]bool) + limExemptOrigins := make([]*regexp.Regexp, 0) + limExemptUserAgents := make([]*regexp.Regexp, 0) if rateLimitConfig.BaseRate > 0 { mainLim = limiterFactory(time.Duration(rateLimitConfig.BaseInterval), rateLimitConfig.BaseRate, "main") for _, origin := range rateLimitConfig.ExemptOrigins { - limExemptOrigins[strings.ToLower(origin)] = true + pattern, err := regexp.Compile(origin) + if err != nil { + return nil, err + } + limExemptOrigins = append(limExemptOrigins, pattern) } for _, agent := range rateLimitConfig.ExemptUserAgents { - limExemptUserAgents[strings.ToLower(agent)] = true + pattern, err := regexp.Compile(agent) + if err != nil { + return nil, err + } + limExemptUserAgents = append(limExemptUserAgents, pattern) } } else { mainLim = NoopFrontendRateLimiter @@ -548,11 +557,22 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context } func (s *Server) isUnlimitedOrigin(origin string) bool { - return s.limExemptOrigins[strings.ToLower(origin)] + for _, pat := range s.limExemptOrigins { + if pat.MatchString(origin) { + return true + } + } + + return false } func (s *Server) isUnlimitedUserAgent(origin string) bool { - return s.limExemptUserAgents[strings.ToLower(origin)] + for _, pat := range s.limExemptUserAgents { + if pat.MatchString(origin) { + return true + } + } + return false } func setCacheHeader(w http.ResponseWriter, cached bool) { From f1b582c385d5f8e0cdaad38dda994e5a5130a4f6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Oct 2022 23:21:20 +0000 Subject: [PATCH 036/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 8 ++++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 80b7fde..3abb130 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,13 @@ # @eth-optimism/proxyd +## 3.12.0 + +### Minor Changes + +- e9f2c701: Allow disabling backend rate limiter +- ca45a85e: Support pattern matching in exempt origins/user agents +- f4faa44c: adds server.log_level config + ## 3.11.0 ### Minor Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 8ee8546..3f96cdf 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.11.0", + "version": "3.12.0", "private": true, "dependencies": {} } From aa3a5c859a3fffbce45493254b583c8ab346c4c3 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sun, 22 Jan 2023 16:40:26 -0700 Subject: [PATCH 037/212] proxyd: Add sender-based rate limiter This PR adds a sender-based rate limiter to proxyd. This rate limiter throttles eth_sendRawTransaction requests based on sender in order to prevent abusive behavior on the sequencer. Integration tests have been added to test this functionality. --- proxyd/proxyd/backend.go | 15 +- proxyd/proxyd/config.go | 37 +++-- .../sender_rate_limit_test.go | 128 ++++++++++++++++++ .../testdata/sender_rate_limit.toml | 23 ++++ .../integration_tests/testdata/testdata.txt | 11 ++ .../integration_tests/validation_test.go | 8 +- proxyd/proxyd/proxyd.go | 10 ++ proxyd/proxyd/server.go | 69 ++++++++++ 8 files changed, 282 insertions(+), 19 deletions(-) create mode 100644 proxyd/proxyd/integration_tests/sender_rate_limit_test.go create mode 100644 proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/testdata.txt diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 2cee810..d043cae 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -78,13 +78,26 @@ var ( Message: "over rate limit", HTTPErrorCode: 429, } + ErrOverSenderRateLimit = &RPCErr{ + Code: JSONRPCErrorInternal - 17, + Message: "sender is over rate limit", + HTTPErrorCode: 429, + } ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") ) func ErrInvalidRequest(msg string) *RPCErr { return &RPCErr{ - Code: -32601, + Code: -32600, + Message: msg, + HTTPErrorCode: 400, + } +} + +func ErrInvalidParams(msg string) *RPCErr { + return &RPCErr{ + Code: -32602, Message: msg, HTTPErrorCode: 400, } diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index ccf050f..c81fec6 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -104,21 +104,30 @@ type BatchConfig struct { ErrorMessage string `toml:"error_message"` } +// SenderRateLimitConfig configures the sender-based rate limiter +// for eth_sendRawTransaction requests. +type SenderRateLimitConfig struct { + Enabled bool + Interval TOMLDuration + Limit int +} + type Config struct { - WSBackendGroup string `toml:"ws_backend_group"` - Server ServerConfig `toml:"server"` - Cache CacheConfig `toml:"cache"` - Redis RedisConfig `toml:"redis"` - Metrics MetricsConfig `toml:"metrics"` - RateLimit RateLimitConfig `toml:"rate_limit"` - BackendOptions BackendOptions `toml:"backend"` - Backends BackendsConfig `toml:"backends"` - BatchConfig BatchConfig `toml:"batch"` - Authentication map[string]string `toml:"authentication"` - BackendGroups BackendGroupsConfig `toml:"backend_groups"` - RPCMethodMappings map[string]string `toml:"rpc_method_mappings"` - WSMethodWhitelist []string `toml:"ws_method_whitelist"` - WhitelistErrorMessage string `toml:"whitelist_error_message"` + WSBackendGroup string `toml:"ws_backend_group"` + Server ServerConfig `toml:"server"` + Cache CacheConfig `toml:"cache"` + Redis RedisConfig `toml:"redis"` + Metrics MetricsConfig `toml:"metrics"` + RateLimit RateLimitConfig `toml:"rate_limit"` + BackendOptions BackendOptions `toml:"backend"` + Backends BackendsConfig `toml:"backends"` + BatchConfig BatchConfig `toml:"batch"` + Authentication map[string]string `toml:"authentication"` + BackendGroups BackendGroupsConfig `toml:"backend_groups"` + RPCMethodMappings map[string]string `toml:"rpc_method_mappings"` + WSMethodWhitelist []string `toml:"ws_method_whitelist"` + WhitelistErrorMessage string `toml:"whitelist_error_message"` + SenderRateLimit SenderRateLimitConfig `toml:"sender_rate_limit"` } func ReadFromEnvOrConfig(value string) (string, error) { diff --git a/proxyd/proxyd/integration_tests/sender_rate_limit_test.go b/proxyd/proxyd/integration_tests/sender_rate_limit_test.go new file mode 100644 index 0000000..b8a7730 --- /dev/null +++ b/proxyd/proxyd/integration_tests/sender_rate_limit_test.go @@ -0,0 +1,128 @@ +package integration_tests + +import ( + "bufio" + "fmt" + "math" + "os" + "strings" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/stretchr/testify/require" +) + +const txHex1 = "0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6c" + + "d17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1" + + "b2774201bac50f627403eac1b732459cf7000000000000000000000000000000" + + "0000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd6" + + "1145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee" + + "08bfac58f58fb3b8bcef5af98578bdeaddf40bde42" + +const txHex2 = "0xf8aa82afd2830f4240830493e094464959ad46e64046b891f562cff202a465d5" + + "22f380b844d5bade070000000000000000000000004200000000000000000000" + + "0000000000000000060000000000000000000000000000000000000000000000" + + "0000000025ef43fc0038a05d8ea9837ea81469bda4dadbe852fdd37fcfbcd666" + + "5641a35e4726fbc04364e7a0107e20bb34aea53c695a551204a11d42fe465055" + + "510ee240e8f884fb70289be6" + +const dummyRes = `{"id": 123, "jsonrpc": "2.0", "result": "dummy"}` + +const limRes = `{"error":{"code":-32017,"message":"sender is over rate limit"},"id":1,"jsonrpc":"2.0"}` + +func TestSenderRateLimitValidation(t *testing.T) { + goodBackend := NewMockBackend(SingleResponseHandler(200, dummyRes)) + defer goodBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + config := ReadConfig("sender_rate_limit") + + // Don't perform rate limiting in this test since we're only testing + // validation. + config.SenderRateLimit.Limit = math.MaxInt + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + f, err := os.Open("testdata/testdata.txt") + require.NoError(t, err) + defer f.Close() + + scanner := bufio.NewScanner(f) + scanner.Scan() // skip header + for scanner.Scan() { + record := strings.Split(scanner.Text(), "|") + name, body, expResponseBody := record[0], record[1], record[2] + require.NoError(t, err) + t.Run(name, func(t *testing.T) { + res, _, err := client.SendRequest([]byte(body)) + require.NoError(t, err) + RequireEqualJSON(t, []byte(expResponseBody), res) + }) + } +} + +func TestSenderRateLimitLimiting(t *testing.T) { + goodBackend := NewMockBackend(SingleResponseHandler(200, dummyRes)) + defer goodBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + config := ReadConfig("sender_rate_limit") + client := NewProxydClient("http://127.0.0.1:8545") + shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + // Two separate requests from the same sender + // should be rate limited. + res1, code1, err := client.SendRequest(makeSendRawTransaction(txHex1)) + require.NoError(t, err) + res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1)) + require.NoError(t, err) + RequireEqualJSON(t, []byte(dummyRes), res1) + require.Equal(t, 200, code1) + RequireEqualJSON(t, []byte(limRes), res2) + require.Equal(t, 429, code2) + + // Clear the limiter. + time.Sleep(1100 * time.Millisecond) + + // Two separate requests from different senders + // should not be rate limited. + res1, code1, err = client.SendRequest(makeSendRawTransaction(txHex1)) + require.NoError(t, err) + res2, code2, err = client.SendRequest(makeSendRawTransaction(txHex2)) + require.NoError(t, err) + RequireEqualJSON(t, []byte(dummyRes), res1) + require.Equal(t, 200, code1) + RequireEqualJSON(t, []byte(dummyRes), res2) + require.Equal(t, 200, code2) + + // Clear the limiter. + time.Sleep(1100 * time.Millisecond) + + // A batch request should rate limit within the batch itself. + batch := []byte(fmt.Sprintf( + `[%s, %s, %s]`, + makeSendRawTransaction(txHex1), + makeSendRawTransaction(txHex1), + makeSendRawTransaction(txHex2), + )) + res, code, err := client.SendRequest(batch) + require.NoError(t, err) + require.Equal(t, 200, code) + RequireEqualJSON(t, []byte(fmt.Sprintf( + `[%s, %s, %s]`, + dummyRes, + limRes, + dummyRes, + )), res) +} + +func makeSendRawTransaction(dataHex string) []byte { + return []byte(`{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["` + dataHex + `"],"id":1}`) +} diff --git a/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml new file mode 100644 index 0000000..840c295 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml @@ -0,0 +1,23 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" +eth_sendRawTransaction = "main" + +[sender_rate_limit] +enabled = true +interval = "1s" +limit = 1 \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/testdata/testdata.txt b/proxyd/proxyd/integration_tests/testdata/testdata.txt new file mode 100644 index 0000000..49d0a87 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/testdata.txt @@ -0,0 +1,11 @@ +name|body|responseBody +not json|not json|{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null} +not json-rpc|{"foo":"bar"}|{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null} +missing fields json-rpc|{"jsonrpc":"2.0"}|{"jsonrpc":"2.0","error":{"code":-32600,"message":"no method specified"},"id":null} +bad method json-rpc|{"jsonrpc":"2.0","method":"eth_notSendRawTransaction","id":1}|{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":1} +no transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"missing value for required argument 0"},"id":1} +invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf6806872fcc650ad4e77e0629206426cd183d751e9ddcc8d5e77"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"rlp: value size exceeds available input length"},"id":1} +invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x1234"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"transaction type not supported"},"id":1} +valid transaction data - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} +valid transaction data - contract call|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6cd17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1b2774201bac50f627403eac1b732459cf70000000000000000000000000000000000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd61145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} +batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":123}] \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/validation_test.go b/proxyd/proxyd/integration_tests/validation_test.go index 6f0653d..f09e2c2 100644 --- a/proxyd/proxyd/integration_tests/validation_test.go +++ b/proxyd/proxyd/integration_tests/validation_test.go @@ -12,10 +12,10 @@ import ( const ( notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted custom message"},"id":999}` parseErrResponse = `{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}` - invalidJSONRPCVersionResponse = `{"error":{"code":-32601,"message":"invalid JSON-RPC version"},"id":null,"jsonrpc":"2.0"}` - invalidIDResponse = `{"error":{"code":-32601,"message":"invalid ID"},"id":null,"jsonrpc":"2.0"}` - invalidMethodResponse = `{"error":{"code":-32601,"message":"no method specified"},"id":null,"jsonrpc":"2.0"}` - invalidBatchLenResponse = `{"error":{"code":-32601,"message":"must specify at least one batch call"},"id":null,"jsonrpc":"2.0"}` + invalidJSONRPCVersionResponse = `{"error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null,"jsonrpc":"2.0"}` + invalidIDResponse = `{"error":{"code":-32600,"message":"invalid ID"},"id":null,"jsonrpc":"2.0"}` + invalidMethodResponse = `{"error":{"code":-32600,"message":"no method specified"},"id":null,"jsonrpc":"2.0"}` + invalidBatchLenResponse = `{"error":{"code":-32600,"message":"must specify at least one batch call"},"id":null,"jsonrpc":"2.0"}` ) func TestSingleRPCValidation(t *testing.T) { diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 3e21d71..ccc6897 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -78,6 +78,15 @@ func Start(config *Config) (func(), error) { ErrTooManyBatchRequests.Message = config.BatchConfig.ErrorMessage } + if config.SenderRateLimit.Enabled { + if config.SenderRateLimit.Limit <= 0 { + return nil, errors.New("limit in sender_rate_limit must be > 0") + } + if time.Duration(config.SenderRateLimit.Interval) < time.Second { + return nil, errors.New("interval in sender_rate_limit must be >= 1s") + } + } + maxConcurrentRPCs := config.Server.MaxConcurrentRPCs if maxConcurrentRPCs == 0 { maxConcurrentRPCs = math.MaxInt64 @@ -244,6 +253,7 @@ func Start(config *Config) (func(), error) { config.Server.MaxUpstreamBatchSize, rpcCache, config.RateLimit, + config.SenderRateLimit, config.Server.EnableRequestLog, config.Server.MaxRequestBodyLogLen, config.BatchConfig.MaxSize, diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 1ee10fa..5eab80e 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -14,6 +14,9 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" "github.com/go-redis/redis/v8" "github.com/gorilla/mux" @@ -50,6 +53,7 @@ type Server struct { upgrader *websocket.Upgrader mainLim FrontendRateLimiter overrideLims map[string]FrontendRateLimiter + senderLim FrontendRateLimiter limExemptOrigins []*regexp.Regexp limExemptUserAgents []*regexp.Regexp rpcServer *http.Server @@ -71,6 +75,7 @@ func NewServer( maxUpstreamBatchSize int, cache RPCCache, rateLimitConfig RateLimitConfig, + senderRateLimitConfig SenderRateLimitConfig, enableRequestLog bool, maxRequestBodyLogLen int, maxBatchSize int, @@ -135,6 +140,10 @@ func NewServer( return nil, err } } + var senderLim FrontendRateLimiter + if senderRateLimitConfig.Enabled { + senderLim = limiterFactory(time.Duration(senderRateLimitConfig.Interval), senderRateLimitConfig.Limit, "senders") + } return &Server{ backendGroups: backendGroups, @@ -154,6 +163,7 @@ func NewServer( }, mainLim: mainLim, overrideLims: overrideLims, + senderLim: senderLim, limExemptOrigins: limExemptOrigins, limExemptUserAgents: limExemptUserAgents, }, nil @@ -409,6 +419,17 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL continue } + // Apply a sender-based rate limit if it is enabled. Note that sender-based rate + // limits apply regardless of origin or user-agent. As such, they don't use the + // isLimited method. + if parsedReq.Method == "eth_sendRawTransaction" && s.senderLim != nil { + if err := s.rateLimitSender(ctx, parsedReq); err != nil { + RecordRPCError(ctx, BackendProxyd, parsedReq.Method, err) + responses[i] = NewRPCErrorRes(parsedReq.ID, err) + continue + } + } + id := string(parsedReq.ID) // If this is a duplicate Request ID, move the Request to a new batchGroup ids[id]++ @@ -575,6 +596,54 @@ func (s *Server) isUnlimitedUserAgent(origin string) bool { return false } +func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { + var params []string + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + log.Debug("error unmarshaling raw transaction params", "err", err, "req_Id", GetReqID(ctx)) + return ErrParseErr + } + + if len(params) != 1 { + log.Debug("raw transaction request has invalid number of params", "req_id", GetReqID(ctx)) + // The error below is identical to the one Geth responds with. + return ErrInvalidParams("missing value for required argument 0") + } + + var data hexutil.Bytes + if err := data.UnmarshalText([]byte(params[0])); err != nil { + log.Debug("error decoding raw tx data", "err", err, "req_id", GetReqID(ctx)) + // Geth returns the raw error from UnmarshalText. + return ErrInvalidParams(err.Error()) + } + + // Inflates a types.Transaction object from the transaction's raw bytes. + tx := new(types.Transaction) + if err := tx.UnmarshalBinary(data); err != nil { + log.Debug("could not unmarshal transaction", "err", err, "req_id", GetReqID(ctx)) + return ErrInvalidParams(err.Error()) + } + + // Convert the transaction into a Message object so that we can get the + // sender. This method performs an ecrecover, which can be expensive. + msg, err := tx.AsMessage(types.LatestSignerForChainID(tx.ChainId()), nil) + if err != nil { + log.Debug("could not get message from transaction", "err", err, "req_id", GetReqID(ctx)) + return ErrInvalidParams(err.Error()) + } + + ok, err := s.senderLim.Take(ctx, msg.From().Hex()) + if err != nil { + log.Error("error taking from sender limiter", "err", err, "req_id", GetReqID(ctx)) + return ErrInternal + } + if !ok { + log.Debug("sender rate limit exceeded", "sender", msg.From(), "req_id", GetReqID(ctx)) + return ErrOverSenderRateLimit + } + + return nil +} + func setCacheHeader(w http.ResponseWriter, cached bool) { if cached { w.Header().Set(cacheStatusHdr, "HIT") From 868d622e4f6e2bca613558c1dc9ff90d92efd57b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Jan 2023 19:35:31 +0000 Subject: [PATCH 038/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 6 ++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 3abb130..80a76e9 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,11 @@ # @eth-optimism/proxyd +## 3.13.0 + +### Minor Changes + +- 6de891d3b: Add sender-based rate limiter + ## 3.12.0 ### Minor Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 3f96cdf..a23eeec 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.12.0", + "version": "3.13.0", "private": true, "dependencies": {} } From 2a87efb68a0aba900fd94bf79fc44cd2eebe122c Mon Sep 17 00:00:00 2001 From: inphi Date: Fri, 27 Jan 2023 13:23:20 -0500 Subject: [PATCH 039/212] proxyd: Add missing req_id to log entry --- proxyd/proxyd/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 5eab80e..2deb15c 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -474,6 +474,7 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL "error forwarding RPC batch", "batch_size", len(elems), "backend_group", group, + "req_id", GetReqID(ctx), "err", err, ) res = nil From d7c80a5a8caf0b6c13599d58b6d4d1fd8cbcf535 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Fri, 27 Jan 2023 12:36:20 -0700 Subject: [PATCH 040/212] proxyd: Add nonce to sender-based rate limit --- proxyd/proxyd/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 5eab80e..af36f1f 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -631,7 +631,7 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { return ErrInvalidParams(err.Error()) } - ok, err := s.senderLim.Take(ctx, msg.From().Hex()) + ok, err := s.senderLim.Take(ctx, fmt.Sprintf("%s:%d", msg.From().Hex(), tx.Nonce())) if err != nil { log.Error("error taking from sender limiter", "err", err, "req_id", GetReqID(ctx)) return ErrInternal From c17bcc9b83bc743bf10304c23637b15b50ff41d3 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 15 Feb 2023 00:42:44 -0700 Subject: [PATCH 041/212] proxyd: Add global flag to method overrides This allows us to set global limits on individual RPCs that ignore any origin/user agent exemption. --- proxyd/proxyd/config.go | 1 + proxyd/proxyd/go.mod | 1 - proxyd/proxyd/go.sum | 2 - .../integration_tests/rate_limit_test.go | 13 ++++ .../testdata/frontend_rate_limit.toml | 8 ++- proxyd/proxyd/server.go | 67 +++++++++++-------- 6 files changed, 61 insertions(+), 31 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index c81fec6..7a004f0 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -55,6 +55,7 @@ type RateLimitConfig struct { type RateLimitMethodOverride struct { Limit int `toml:"limit"` Interval TOMLDuration `toml:"interval"` + Global bool `toml:"global"` } type TOMLDuration time.Duration diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 0a866b8..be300c1 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -13,7 +13,6 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/prometheus/client_golang v1.11.0 github.com/rs/cors v1.8.2 - github.com/sethvargo/go-limiter v0.7.2 github.com/stretchr/testify v1.7.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c ) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 8e5fd8d..15350df 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -451,8 +451,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sethvargo/go-limiter v0.7.2 h1:FgC4N7RMpV5gMrUdda15FaFTkQ/L4fEqM7seXMs4oO8= -github.com/sethvargo/go-limiter v0.7.2/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 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= diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/proxyd/integration_tests/rate_limit_test.go index e4cc698..7a70dea 100644 --- a/proxyd/proxyd/integration_tests/rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/rate_limit_test.go @@ -139,6 +139,19 @@ func TestFrontendMaxRPSLimit(t *testing.T) { require.Nil(t, res[1].Error) require.Nil(t, res[2].Error) }) + + time.Sleep(time.Second) + + t.Run("global RPC override", func(t *testing.T) { + h := make(http.Header) + h.Set("User-Agent", "exempt_agent") + client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) + limitedRes, codes := spamReqs(t, client, "eth_baz", 429, 2) + // use 1 and 1 here since the limit for eth_baz is 1 + require.Equal(t, 1, codes[429]) + require.Equal(t, 1, codes[200]) + RequireEqualJSON(t, []byte(frontendOverLimitResponseWithID), limitedRes) + }) } func spamReqs(t *testing.T, client *ProxydHTTPClient, method string, limCode int, n int) ([]byte, map[int]int) { diff --git a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml index affb855..8aa9d19 100644 --- a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml @@ -16,6 +16,7 @@ backends = ["good"] [rpc_method_mappings] eth_chainId = "main" eth_foobar = "main" +eth_baz = "main" [rate_limit] base_rate = 2 @@ -26,4 +27,9 @@ error_message = "over rate limit with special message" [rate_limit.method_overrides.eth_foobar] limit = 1 -interval = "1s" \ No newline at end of file +interval = "1s" + +[rate_limit.method_overrides.eth_baz] +limit = 1 +interval = "1s" +global = true \ No newline at end of file diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 65f1047..5502d74 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -39,27 +39,28 @@ const ( var emptyArrayResponse = json.RawMessage("[]") type Server struct { - backendGroups map[string]*BackendGroup - wsBackendGroup *BackendGroup - wsMethodWhitelist *StringSet - rpcMethodMappings map[string]string - maxBodySize int64 - enableRequestLog bool - maxRequestBodyLogLen int - authenticatedPaths map[string]string - timeout time.Duration - maxUpstreamBatchSize int - maxBatchSize int - upgrader *websocket.Upgrader - mainLim FrontendRateLimiter - overrideLims map[string]FrontendRateLimiter - senderLim FrontendRateLimiter - limExemptOrigins []*regexp.Regexp - limExemptUserAgents []*regexp.Regexp - rpcServer *http.Server - wsServer *http.Server - cache RPCCache - srvMu sync.Mutex + backendGroups map[string]*BackendGroup + wsBackendGroup *BackendGroup + wsMethodWhitelist *StringSet + rpcMethodMappings map[string]string + maxBodySize int64 + enableRequestLog bool + maxRequestBodyLogLen int + authenticatedPaths map[string]string + timeout time.Duration + maxUpstreamBatchSize int + maxBatchSize int + upgrader *websocket.Upgrader + mainLim FrontendRateLimiter + overrideLims map[string]FrontendRateLimiter + senderLim FrontendRateLimiter + limExemptOrigins []*regexp.Regexp + limExemptUserAgents []*regexp.Regexp + globallyLimitedMethods map[string]bool + rpcServer *http.Server + wsServer *http.Server + cache RPCCache + srvMu sync.Mutex } type limiterFunc func(method string) bool @@ -133,12 +134,17 @@ func NewServer( } overrideLims := make(map[string]FrontendRateLimiter) + globalMethodLims := make(map[string]bool) for method, override := range rateLimitConfig.MethodOverrides { var err error overrideLims[method] = limiterFactory(time.Duration(override.Interval), override.Limit, method) if err != nil { return nil, err } + + if override.Global { + globalMethodLims[method] = true + } } var senderLim FrontendRateLimiter if senderRateLimitConfig.Enabled { @@ -161,11 +167,12 @@ func NewServer( upgrader: &websocket.Upgrader{ HandshakeTimeout: 5 * time.Second, }, - mainLim: mainLim, - overrideLims: overrideLims, - senderLim: senderLim, - limExemptOrigins: limExemptOrigins, - limExemptUserAgents: limExemptUserAgents, + mainLim: mainLim, + overrideLims: overrideLims, + globallyLimitedMethods: globalMethodLims, + senderLim: senderLim, + limExemptOrigins: limExemptOrigins, + limExemptUserAgents: limExemptUserAgents, }, nil } @@ -243,7 +250,9 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { } isLimited := func(method string) bool { - if isUnlimitedOrigin || isUnlimitedUserAgent { + isGloballyLimitedMethod := s.isGlobalLimit(method) + fmt.Println(method, isGloballyLimitedMethod) + if !isGloballyLimitedMethod && (isUnlimitedOrigin || isUnlimitedUserAgent) { return false } @@ -597,6 +606,10 @@ func (s *Server) isUnlimitedUserAgent(origin string) bool { return false } +func (s *Server) isGlobalLimit(method string) bool { + return s.globallyLimitedMethods[method] +} + func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { var params []string if err := json.Unmarshal(req.Params, ¶ms); err != nil { From 4037ef8b2855234c15b93a0cba0b7f938ba9419a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Feb 2023 16:36:28 +0000 Subject: [PATCH 042/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 11 +++++++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 80a76e9..536277c 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,16 @@ # @eth-optimism/proxyd +## 3.14.0 + +### Minor Changes + +- 9cc39bcfa: Add support for global method override rate limit +- 30db32862: Include nonce in sender rate limit + +### Patch Changes + +- b9bb1a98a: proxyd: Add req_id to log + ## 3.13.0 ### Minor Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index a23eeec..20e698c 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.13.0", + "version": "3.14.0", "private": true, "dependencies": {} } From 336c3a7c54eac5f22b0e9e80aa4c4e31fb984dcb Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 15 Feb 2023 10:05:51 -0700 Subject: [PATCH 043/212] proxyd: Remove logging --- proxyd/proxyd/server.go | 1 - 1 file changed, 1 deletion(-) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 5502d74..5df9d37 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -251,7 +251,6 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { isLimited := func(method string) bool { isGloballyLimitedMethod := s.isGlobalLimit(method) - fmt.Println(method, isGloballyLimitedMethod) if !isGloballyLimitedMethod && (isUnlimitedOrigin || isUnlimitedUserAgent) { return false } From 2fc790aeda9a1ca71cbf9904abc77e5dab4fc415 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Feb 2023 20:01:27 +0000 Subject: [PATCH 044/212] chore(deps): bump github.com/prometheus/client_golang in /proxyd Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.11.0 to 1.11.1. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.11.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- proxyd/proxyd/go.mod | 2 +- proxyd/proxyd/go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index be300c1..01bfaeb 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d - github.com/prometheus/client_golang v1.11.0 + github.com/prometheus/client_golang v1.11.1 github.com/rs/cors v1.8.2 github.com/stretchr/testify v1.7.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 15350df..ec6eacc 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -414,8 +414,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= From efe1879957026a73a11d6389cd620a59d9a4b264 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Feb 2023 01:53:04 +0000 Subject: [PATCH 045/212] Version Packages --- proxyd/proxyd/CHANGELOG.md | 7 +++++++ proxyd/proxyd/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/proxyd/CHANGELOG.md index 536277c..dd78bfe 100644 --- a/proxyd/proxyd/CHANGELOG.md +++ b/proxyd/proxyd/CHANGELOG.md @@ -1,5 +1,12 @@ # @eth-optimism/proxyd +## 3.14.1 + +### Patch Changes + +- 5602deec7: chore(deps): bump github.com/prometheus/client_golang from 1.11.0 to 1.11.1 in /proxyd +- 6b3cf2070: Remove useless logging + ## 3.14.0 ### Minor Changes diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json index 20e698c..a2d8ef3 100644 --- a/proxyd/proxyd/package.json +++ b/proxyd/proxyd/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/proxyd", - "version": "3.14.0", + "version": "3.14.1", "private": true, "dependencies": {} } From d4655c6cb9e68812089a079caae174472a29c226 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 22 Feb 2023 18:53:23 -0700 Subject: [PATCH 046/212] ci: Remove proxyd and indexer from changesets workflow --- proxyd/proxyd/package.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 proxyd/proxyd/package.json diff --git a/proxyd/proxyd/package.json b/proxyd/proxyd/package.json deleted file mode 100644 index a2d8ef3..0000000 --- a/proxyd/proxyd/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@eth-optimism/proxyd", - "version": "3.14.1", - "private": true, - "dependencies": {} -} From 9aa85e10fbc0cdced13beb3ec621cb8e823d92e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Feb 2023 11:41:45 +0000 Subject: [PATCH 047/212] build(deps): bump golang.org/x/sys in /proxyd Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20220310020820-b874c991c1a5 to 0.1.0. - [Release notes](https://github.com/golang/sys/releases) - [Commits](https://github.com/golang/sys/commits/v0.1.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: indirect ... Signed-off-by: dependabot[bot] --- proxyd/proxyd/go.mod | 2 +- proxyd/proxyd/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 01bfaeb..d47c7a3 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -58,7 +58,7 @@ require ( github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect - golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index ec6eacc..6e90470 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -683,8 +683,8 @@ golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 5f6df9ac5e7f4a5c9b810d8ec28d1b7b266f47ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:11:28 +0000 Subject: [PATCH 048/212] build(deps): bump golang.org/x/crypto in /proxyd Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20220307211146-efcb8507fb70 to 0.1.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/commits/v0.1.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] --- proxyd/proxyd/go.mod | 2 +- proxyd/proxyd/go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index d47c7a3..d73d6fc 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -57,7 +57,7 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect + golang.org/x/crypto v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 6e90470..4db2ccf 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -524,8 +524,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= -golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -602,7 +602,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -695,8 +695,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 05536fa181263df02e274c22e6533a6158701d42 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 18 Apr 2023 11:57:55 -0700 Subject: [PATCH 049/212] proxyd: add consensus poller --- proxyd/proxyd/backend.go | 35 +- proxyd/proxyd/cmd/proxyd/main.go | 2 +- proxyd/proxyd/config.go | 5 +- proxyd/proxyd/consensus_poller.go | 345 ++++++++++++++++++ proxyd/proxyd/consensus_poller_test.go | 74 ++++ proxyd/proxyd/consensus_tracker.go | 70 ++++ proxyd/proxyd/example.config.toml | 1 + proxyd/proxyd/go.mod | 2 + .../integration_tests/batch_timeout_test.go | 2 +- .../proxyd/integration_tests/batching_test.go | 2 +- .../proxyd/integration_tests/caching_test.go | 4 +- .../integration_tests/consensus_test.go | 309 ++++++++++++++++ .../proxyd/integration_tests/failover_test.go | 10 +- .../integration_tests/max_rpc_conns_test.go | 2 +- .../integration_tests/rate_limit_test.go | 4 +- .../sender_rate_limit_test.go | 4 +- .../integration_tests/testdata/consensus.toml | 24 ++ .../testdata/consensus_responses.yml | 44 +++ .../integration_tests/validation_test.go | 4 +- proxyd/proxyd/integration_tests/ws_test.go | 8 +- proxyd/proxyd/metrics.go | 14 + proxyd/proxyd/proxyd.go | 73 ++-- proxyd/proxyd/server.go | 8 +- .../tools/mockserver/handler/handler.go | 102 ++++++ proxyd/proxyd/tools/mockserver/main.go | 30 ++ proxyd/proxyd/tools/mockserver/node1.yml | 44 +++ proxyd/proxyd/tools/mockserver/node2.yml | 44 +++ 27 files changed, 1209 insertions(+), 57 deletions(-) create mode 100644 proxyd/proxyd/consensus_poller.go create mode 100644 proxyd/proxyd/consensus_poller_test.go create mode 100644 proxyd/proxyd/consensus_tracker.go create mode 100644 proxyd/proxyd/integration_tests/consensus_test.go create mode 100644 proxyd/proxyd/integration_tests/testdata/consensus.toml create mode 100644 proxyd/proxyd/integration_tests/testdata/consensus_responses.yml create mode 100644 proxyd/proxyd/tools/mockserver/handler/handler.go create mode 100644 proxyd/proxyd/tools/mockserver/main.go create mode 100644 proxyd/proxyd/tools/mockserver/node1.yml create mode 100644 proxyd/proxyd/tools/mockserver/node2.yml diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index d043cae..5e8c632 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -365,6 +365,36 @@ func (b *Backend) setOffline() { } } +// ForwardRPC makes a call directly to a backend and populate the response into `res` +func (b *Backend) ForwardRPC(ctx context.Context, res *RPCRes, id string, method string, params ...any) error { + jsonParams, err := json.Marshal(params) + if err != nil { + return err + } + + rpcReq := RPCReq{ + JSONRPC: JSONRPCVersion, + Method: method, + Params: jsonParams, + ID: []byte(id), + } + + slicedRes, err := b.doForward(ctx, []*RPCReq{&rpcReq}, false) + if err != nil { + return err + } + + if len(slicedRes) != 1 { + return fmt.Errorf("unexpected response len for non-batched request (len != 1)") + } + if slicedRes[0].IsError() { + return fmt.Errorf(slicedRes[0].Error.Error()) + } + + *res = *(slicedRes[0]) + return nil +} + func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { isSingleElementBatch := len(rpcReqs) == 1 @@ -484,8 +514,9 @@ func sortBatchRPCResponse(req []*RPCReq, res []*RPCRes) { } type BackendGroup struct { - Name string - Backends []*Backend + Name string + Backends []*Backend + Consensus *ConsensusPoller } func (b *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { diff --git a/proxyd/proxyd/cmd/proxyd/main.go b/proxyd/proxyd/cmd/proxyd/main.go index c184a1d..a97aacb 100644 --- a/proxyd/proxyd/cmd/proxyd/main.go +++ b/proxyd/proxyd/cmd/proxyd/main.go @@ -52,7 +52,7 @@ func main() { ), ) - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) if err != nil { log.Crit("error starting proxyd", "err", err) } diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 7a004f0..94c1f83 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -82,6 +82,7 @@ type BackendConfig struct { Password string `toml:"password"` RPCURL string `toml:"rpc_url"` WSURL string `toml:"ws_url"` + WSPort int `toml:"ws_port"` MaxRPS int `toml:"max_rps"` MaxWSConns int `toml:"max_ws_conns"` CAFile string `toml:"ca_file"` @@ -93,7 +94,9 @@ type BackendConfig struct { type BackendsConfig map[string]*BackendConfig type BackendGroupConfig struct { - Backends []string `toml:"backends"` + Backends []string `toml:"backends"` + ConsensusAware bool `toml:"consensus_aware"` + ConsensusAsyncHandler string `toml:"consensus_handler"` } type BackendGroupsConfig map[string]*BackendGroupConfig diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go new file mode 100644 index 0000000..81f8fc3 --- /dev/null +++ b/proxyd/proxyd/consensus_poller.go @@ -0,0 +1,345 @@ +package proxyd + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum/go-ethereum/log" +) + +const ( + PollerInterval = 1 * time.Second +) + +// ConsensusPoller checks the consensus state for each member of a BackendGroup +// resolves the highest common block for multiple nodes, and reconciles the consensus +// in case of block hash divergence to minimize re-orgs +type ConsensusPoller struct { + cancelFunc context.CancelFunc + + backendGroup *BackendGroup + backendState map[*Backend]*backendState + consensusGroupMux sync.Mutex + consensusGroup []*Backend + + tracker ConsensusTracker + asyncHandler ConsensusAsyncHandler +} + +type backendState struct { + backendStateMux sync.Mutex + + latestBlockNumber string + latestBlockHash string + + lastUpdate time.Time + + bannedUntil time.Time +} + +// GetConsensusGroup returns the backend members that are agreeing in a consensus +func (cp *ConsensusPoller) GetConsensusGroup() []*Backend { + defer cp.consensusGroupMux.Unlock() + cp.consensusGroupMux.Lock() + + g := make([]*Backend, len(cp.backendGroup.Backends)) + copy(g, cp.consensusGroup) + + return g +} + +// GetConsensusBlockNumber returns the agreed block number in a consensus +func (ct *ConsensusPoller) GetConsensusBlockNumber() string { + return ct.tracker.GetConsensusBlockNumber() +} + +func (cp *ConsensusPoller) Shutdown() { + cp.asyncHandler.Shutdown() +} + +// ConsensusAsyncHandler controls the asynchronous polling mechanism, interval and shutdown +type ConsensusAsyncHandler interface { + Init() + Shutdown() +} + +// NoopAsyncHandler allows fine control updating the consensus +type NoopAsyncHandler struct{} + +func NewNoopAsyncHandler() ConsensusAsyncHandler { + log.Warn("using NewNoopAsyncHandler") + return &NoopAsyncHandler{} +} +func (ah *NoopAsyncHandler) Init() {} +func (ah *NoopAsyncHandler) Shutdown() {} + +// PollerAsyncHandler asynchronously updates each individual backend and the group consensus +type PollerAsyncHandler struct { + ctx context.Context + cp *ConsensusPoller +} + +func NewPollerAsyncHandler(ctx context.Context, cp *ConsensusPoller) ConsensusAsyncHandler { + return &PollerAsyncHandler{ + ctx: ctx, + cp: cp, + } +} +func (ah *PollerAsyncHandler) Init() { + // create the individual backend pollers + for _, be := range ah.cp.backendGroup.Backends { + go func(be *Backend) { + for { + timer := time.NewTimer(PollerInterval) + ah.cp.UpdateBackend(ah.ctx, be) + + select { + case <-timer.C: + case <-ah.ctx.Done(): + timer.Stop() + return + } + } + }(be) + } + + // create the group consensus poller + go func() { + for { + timer := time.NewTimer(PollerInterval) + ah.cp.UpdateBackendGroupConsensus(ah.ctx) + + select { + case <-timer.C: + case <-ah.ctx.Done(): + timer.Stop() + return + } + } + }() +} +func (ah *PollerAsyncHandler) Shutdown() { + ah.cp.cancelFunc() +} + +type ConsensusOpt func(cp *ConsensusPoller) + +func WithTracker(tracker ConsensusTracker) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.tracker = tracker + } +} + +func WithAsyncHandler(asyncHandler ConsensusAsyncHandler) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.asyncHandler = asyncHandler + } +} + +func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller { + ctx, cancelFunc := context.WithCancel(context.Background()) + + state := make(map[*Backend]*backendState, len(bg.Backends)) + for _, be := range bg.Backends { + state[be] = &backendState{} + } + + cp := &ConsensusPoller{ + cancelFunc: cancelFunc, + backendGroup: bg, + backendState: state, + } + + for _, opt := range opts { + opt(cp) + } + + if cp.tracker == nil { + cp.tracker = NewInMemoryConsensusTracker() + } + + if cp.asyncHandler == nil { + cp.asyncHandler = NewPollerAsyncHandler(ctx, cp) + } + + cp.asyncHandler.Init() + + return cp +} + +// UpdateBackend refreshes the consensus state of a single backend +func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { + bs := cp.backendState[be] + if time.Now().Before(bs.bannedUntil) { + log.Warn("skipping backend banned", "backend", be.Name, "bannedUntil", bs.bannedUntil) + return + } + + if be.IsRateLimited() || !be.Online() { + return + } + + // we'll introduce here checks to ban the backend + // i.e. node is syncing the chain + + // then update backend consensus + + latestBlockNumber, latestBlockHash, err := cp.fetchBlock(ctx, be, "latest") + if err != nil { + log.Warn("error updating backend", "name", be.Name, "err", err) + return + } + + changed := cp.setBackendState(be, latestBlockNumber, latestBlockHash) + + if changed { + backendLatestBlockBackend.WithLabelValues(be.Name).Set(blockToFloat(latestBlockNumber)) + log.Info("backend state updated", "name", be.Name, "state", bs) + } +} + +// UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends +func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { + var lowestBlock string + var lowestBlockHash string + + currentConsensusBlockNumber := cp.GetConsensusBlockNumber() + + for _, be := range cp.backendGroup.Backends { + backendLatestBlockNumber, backendLatestBlockHash := cp.getBackendState(be) + if lowestBlock == "" || backendLatestBlockNumber < lowestBlock { + lowestBlock = backendLatestBlockNumber + lowestBlockHash = backendLatestBlockHash + } + } + + // no block to propose (i.e. initializing consensus) + if lowestBlock == "" { + return + } + + proposedBlock := lowestBlock + proposedBlockHash := lowestBlockHash + hasConsensus := false + + // check if everybody agrees on the same block hash + consensusBackends := make([]*Backend, 0, len(cp.backendGroup.Backends)) + consensusBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) + filteredBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) + + if lowestBlock > currentConsensusBlockNumber { + log.Info("validating consensus on block", lowestBlock) + } + + broken := false + for !hasConsensus { + allAgreed := true + consensusBackends = consensusBackends[:0] + filteredBackendsNames = filteredBackendsNames[:0] + for _, be := range cp.backendGroup.Backends { + if be.IsRateLimited() || !be.Online() || time.Now().Before(cp.backendState[be].bannedUntil) { + filteredBackendsNames = append(filteredBackendsNames, be.Name) + continue + } + + actualBlockNumber, actualBlockHash, err := cp.fetchBlock(ctx, be, proposedBlock) + if err != nil { + log.Warn("error updating backend", "name", be.Name, "err", err) + continue + } + if proposedBlockHash == "" { + proposedBlockHash = actualBlockHash + } + blocksDontMatch := (actualBlockNumber != proposedBlock) || (actualBlockHash != proposedBlockHash) + if blocksDontMatch { + if blockAheadOrEqual(currentConsensusBlockNumber, actualBlockNumber) { + log.Warn("backend broke consensus", "name", be.Name, "blockNum", actualBlockNumber, "proposedBlockNum", proposedBlock, "blockHash", actualBlockHash, "proposedBlockHash", proposedBlockHash) + broken = true + } + allAgreed = false + break + } + consensusBackends = append(consensusBackends, be) + consensusBackendsNames = append(consensusBackendsNames, be.Name) + } + if allAgreed { + hasConsensus = true + } else { + // walk one block behind and try again + proposedBlock = hexAdd(proposedBlock, -1) + proposedBlockHash = "" + log.Info("no consensus, now trying", "block:", proposedBlock) + } + } + + if broken { + // propagate event to other interested parts, such as cache invalidator + log.Info("consensus broken", "currentConsensusBlockNumber", currentConsensusBlockNumber, "proposedBlock", proposedBlock, "proposedBlockHash", proposedBlockHash) + } + + cp.tracker.SetConsensusBlockNumber(proposedBlock) + consensusLatestBlock.Set(blockToFloat(proposedBlock)) + cp.consensusGroupMux.Lock() + cp.consensusGroup = consensusBackends + cp.consensusGroupMux.Unlock() + + log.Info("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) +} + +// fetchBlock Convenient wrapper to make a request to get a block directly from the backend +func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block string) (blockNumber string, blockHash string, err error) { + var rpcRes RPCRes + err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_getBlockByNumber", block, false) + if err != nil { + return "", "", err + } + + jsonMap, ok := rpcRes.Result.(map[string]interface{}) + if !ok { + return "", "", fmt.Errorf(fmt.Sprintf("unexpected response type checking consensus on backend %s", be.Name)) + } + blockNumber = jsonMap["number"].(string) + blockHash = jsonMap["hash"].(string) + + return +} + +func (cp *ConsensusPoller) getBackendState(be *Backend) (blockNumber string, blockHash string) { + bs := cp.backendState[be] + bs.backendStateMux.Lock() + blockNumber = bs.latestBlockNumber + blockHash = bs.latestBlockHash + bs.backendStateMux.Unlock() + return +} + +func (cp *ConsensusPoller) setBackendState(be *Backend, blockNumber string, blockHash string) (changed bool) { + bs := cp.backendState[be] + bs.backendStateMux.Lock() + changed = bs.latestBlockHash != blockHash + bs.latestBlockNumber = blockNumber + bs.latestBlockHash = blockHash + bs.lastUpdate = time.Now() + bs.backendStateMux.Unlock() + return +} + +// hexAdd Convenient way to convert hex block to uint64, increment, and convert back to hex +func hexAdd(hexVal string, incr int64) string { + return hexutil.EncodeUint64(uint64(int64(hexutil.MustDecodeUint64(hexVal)) + incr)) +} + +// blockAheadOrEqual Convenient way to check if `baseBlock` is ahead or equal than `checkBlock` +func blockAheadOrEqual(baseBlock string, checkBlock string) bool { + return hexutil.MustDecodeUint64(baseBlock) >= hexutil.MustDecodeUint64(checkBlock) +} + +// blockToFloat Convenient way to convert a hex block to float64 +func blockToFloat(hexVal string) float64 { + return float64(hexutil.MustDecodeUint64(hexVal)) +} diff --git a/proxyd/proxyd/consensus_poller_test.go b/proxyd/proxyd/consensus_poller_test.go new file mode 100644 index 0000000..06680fa --- /dev/null +++ b/proxyd/proxyd/consensus_poller_test.go @@ -0,0 +1,74 @@ +package proxyd + +import ( + "testing" +) + +func Test_blockToFloat(t *testing.T) { + type args struct { + hexVal string + } + tests := []struct { + name string + args args + want float64 + }{ + {"0xf1b3", args{"0xf1b3"}, float64(61875)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := blockToFloat(tt.args.hexVal); got != tt.want { + t.Errorf("blockToFloat() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_hexAdd(t *testing.T) { + type args struct { + hexVal string + incr int64 + } + tests := []struct { + name string + args args + want string + }{ + {"0x1", args{"0x1", 1}, "0x2"}, + {"0x2", args{"0x2", -1}, "0x1"}, + {"0xf", args{"0xf", 1}, "0x10"}, + {"0x10", args{"0x10", -1}, "0xf"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := hexAdd(tt.args.hexVal, tt.args.incr); got != tt.want { + t.Errorf("hexAdd() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_blockAheadOrEqual(t *testing.T) { + type args struct { + baseBlock string + checkBlock string + } + tests := []struct { + name string + args args + want bool + }{ + {"0x1 vs 0x1", args{"0x1", "0x1"}, true}, + {"0x2 vs 0x1", args{"0x2", "0x1"}, true}, + {"0x1 vs 0x2", args{"0x1", "0x2"}, false}, + {"0xff vs 0x100", args{"0xff", "0x100"}, false}, + {"0x100 vs 0xff", args{"0x100", "0xff"}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := blockAheadOrEqual(tt.args.baseBlock, tt.args.checkBlock); got != tt.want { + t.Errorf("blockAheadOrEqual() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go new file mode 100644 index 0000000..7218b32 --- /dev/null +++ b/proxyd/proxyd/consensus_tracker.go @@ -0,0 +1,70 @@ +package proxyd + +import ( + "context" + "fmt" + "sync" + + "github.com/go-redis/redis/v8" +) + +// ConsensusTracker abstracts how we store and retrieve the current consensus +// allowing it to be stored locally in-memory or in a shared Redis cluster +type ConsensusTracker interface { + GetConsensusBlockNumber() string + SetConsensusBlockNumber(blockNumber string) +} + +// InMemoryConsensusTracker store and retrieve in memory, async-safe +type InMemoryConsensusTracker struct { + consensusBlockNumber string + mutex sync.Mutex +} + +func NewInMemoryConsensusTracker() ConsensusTracker { + return &InMemoryConsensusTracker{ + consensusBlockNumber: "", // empty string semantics means unknown + mutex: sync.Mutex{}, + } +} + +func (ct *InMemoryConsensusTracker) GetConsensusBlockNumber() string { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + return ct.consensusBlockNumber +} + +func (ct *InMemoryConsensusTracker) SetConsensusBlockNumber(blockNumber string) { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + ct.consensusBlockNumber = blockNumber +} + +// RedisConsensusTracker uses a Redis `client` to store and retrieve consensus, async-safe +type RedisConsensusTracker struct { + ctx context.Context + client *redis.Client + backendGroup string +} + +func NewRedisConsensusTracker(ctx context.Context, r *redis.Client, namespace string) ConsensusTracker { + return &RedisConsensusTracker{ + ctx: ctx, + client: r, + backendGroup: namespace, + } +} + +func (ct *RedisConsensusTracker) key() string { + return fmt.Sprintf("consensus_latest_block:%s", ct.backendGroup) +} + +func (ct *RedisConsensusTracker) GetConsensusBlockNumber() string { + return ct.client.Get(ct.ctx, ct.key()).Val() +} + +func (ct *RedisConsensusTracker) SetConsensusBlockNumber(blockNumber string) { + ct.client.Set(ct.ctx, ct.key(), blockNumber, 0) +} diff --git a/proxyd/proxyd/example.config.toml b/proxyd/proxyd/example.config.toml index fb8fea9..16408e8 100644 --- a/proxyd/proxyd/example.config.toml +++ b/proxyd/proxyd/example.config.toml @@ -15,6 +15,7 @@ rpc_port = 8080 # Host for the proxyd WS server to listen on. ws_host = "0.0.0.0" # Port for the above +# Set the ws_port to 0 to disable WS ws_port = 8085 # Maximum client body size, in bytes, that the server will accept. max_body_size_bytes = 10485760 diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index d73d6fc..68bf63c 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -11,10 +11,12 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.11.1 github.com/rs/cors v1.8.2 github.com/stretchr/testify v1.7.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + gopkg.in/yaml.v2 v2.4.0 ) require ( diff --git a/proxyd/proxyd/integration_tests/batch_timeout_test.go b/proxyd/proxyd/integration_tests/batch_timeout_test.go index 372f047..4906c1d 100644 --- a/proxyd/proxyd/integration_tests/batch_timeout_test.go +++ b/proxyd/proxyd/integration_tests/batch_timeout_test.go @@ -22,7 +22,7 @@ func TestBatchTimeout(t *testing.T) { config := ReadConfig("batch_timeout") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/proxyd/proxyd/integration_tests/batching_test.go b/proxyd/proxyd/integration_tests/batching_test.go index b0f811c..e40745d 100644 --- a/proxyd/proxyd/integration_tests/batching_test.go +++ b/proxyd/proxyd/integration_tests/batching_test.go @@ -148,7 +148,7 @@ func TestBatching(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/proxyd/proxyd/integration_tests/caching_test.go b/proxyd/proxyd/integration_tests/caching_test.go index a75a591..b0a2f20 100644 --- a/proxyd/proxyd/integration_tests/caching_test.go +++ b/proxyd/proxyd/integration_tests/caching_test.go @@ -35,7 +35,7 @@ func TestCaching(t *testing.T) { require.NoError(t, os.Setenv("REDIS_URL", fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()))) config := ReadConfig("caching") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -171,7 +171,7 @@ func TestBatchCaching(t *testing.T) { config := ReadConfig("caching") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go new file mode 100644 index 0000000..1ed7d12 --- /dev/null +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -0,0 +1,309 @@ +package integration_tests + +import ( + "context" + "fmt" + "net/http" + "os" + "path" + "testing" + + "github.com/ethereum-optimism/optimism/proxyd" + ms "github.com/ethereum-optimism/optimism/proxyd/tools/mockserver/handler" + "github.com/stretchr/testify/require" +) + +func TestConsensus(t *testing.T) { + node1 := NewMockBackend(nil) + defer node1.Close() + node2 := NewMockBackend(nil) + defer node2.Close() + + dir, err := os.Getwd() + require.NoError(t, err) + + responses := path.Join(dir, "testdata/consensus_responses.yml") + + h1 := ms.MockedHandler{ + Overrides: []*ms.MethodTemplate{}, + Autoload: true, + AutoloadFile: responses, + } + h2 := ms.MockedHandler{ + Overrides: []*ms.MethodTemplate{}, + Autoload: true, + AutoloadFile: responses, + } + + require.NoError(t, os.Setenv("NODE1_URL", node1.URL())) + require.NoError(t, os.Setenv("NODE2_URL", node2.URL())) + + node1.SetHandler(http.HandlerFunc(h1.Handler)) + node2.SetHandler(http.HandlerFunc(h2.Handler)) + + config := ReadConfig("consensus") + ctx := context.Background() + svr, shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + bg := svr.BackendGroups["node"] + require.NotNil(t, bg) + require.NotNil(t, bg.Consensus) + + t.Run("initial consensus", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + + // unknown consensus at init + require.Equal(t, "", bg.Consensus.GetConsensusBlockNumber()) + + // first poll + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // consensus at block 0x1 + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + }) + + t.Run("advance consensus", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // all nodes start at block 0x1 + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + + // advance latest on node2 to 0x2 + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x2", "hash2"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // consensus should stick to 0x1, since node1 is still lagging there + bg.Consensus.UpdateBackendGroupConsensus(ctx) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + + // advance latest on node1 to 0x2 + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x2", "hash2"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // should stick to 0x2, since now all nodes are at 0x2 + require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber()) + }) + + t.Run("broken consensus", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // all nodes start at block 0x1 + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + + // advance latest on both nodes to 0x2 + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x2", "hash2"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x2", "hash2"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // at 0x2 + require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber()) + + // make node2 diverge on hash + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x2", + Response: buildResponse("0x2", "wrong_hash"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // should resolve to 0x1, since 0x2 is out of consensus at the moment + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + + // later, when impl events, listen to broken consensus event + }) + + t.Run("broken consensus with depth 2", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // all nodes start at block 0x1 + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + + // advance latest on both nodes to 0x2 + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x2", "hash2"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x2", "hash2"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // at 0x2 + require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber()) + + // advance latest on both nodes to 0x3 + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x3", "hash3"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x3", "hash3"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // at 0x3 + require.Equal(t, "0x3", bg.Consensus.GetConsensusBlockNumber()) + + // make node2 diverge on hash for blocks 0x2 and 0x3 + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x2", + Response: buildResponse("0x2", "wrong_hash2"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x3", + Response: buildResponse("0x3", "wrong_hash3"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // should resolve to 0x1 + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + }) + + t.Run("fork in advanced block", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // all nodes start at block 0x1 + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + + // make nodes 1 and 2 advance in forks + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x2", + Response: buildResponse("0x2", "node1_0x2"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x2", + Response: buildResponse("0x2", "node2_0x2"), + }) + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x3", + Response: buildResponse("0x3", "node1_0x3"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x3", + Response: buildResponse("0x3", "node2_0x3"), + }) + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x3", "node1_0x3"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildResponse("0x3", "node2_0x3"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // should resolve to 0x1, the highest common ancestor + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + }) +} + +func buildResponse(number string, hash string) string { + return fmt.Sprintf(`{ + "jsonrpc": "2.0", + "id": 67, + "result": { + "number": "%s", + "hash": "%s" + } + }`, number, hash) +} diff --git a/proxyd/proxyd/integration_tests/failover_test.go b/proxyd/proxyd/integration_tests/failover_test.go index 47c9e26..119a901 100644 --- a/proxyd/proxyd/integration_tests/failover_test.go +++ b/proxyd/proxyd/integration_tests/failover_test.go @@ -30,7 +30,7 @@ func TestFailover(t *testing.T) { config := ReadConfig("failover") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -128,7 +128,7 @@ func TestRetries(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) config := ReadConfig("retries") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -171,7 +171,7 @@ func TestOutOfServiceInterval(t *testing.T) { config := ReadConfig("out_of_service_interval") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -226,7 +226,7 @@ func TestBatchWithPartialFailover(t *testing.T) { require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -273,7 +273,7 @@ func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/proxyd/proxyd/integration_tests/max_rpc_conns_test.go b/proxyd/proxyd/integration_tests/max_rpc_conns_test.go index 1ee1feb..5e23364 100644 --- a/proxyd/proxyd/integration_tests/max_rpc_conns_test.go +++ b/proxyd/proxyd/integration_tests/max_rpc_conns_test.go @@ -41,7 +41,7 @@ func TestMaxConcurrentRPCs(t *testing.T) { config := ReadConfig("max_rpc_conns") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/proxyd/integration_tests/rate_limit_test.go index 7a70dea..dd69089 100644 --- a/proxyd/proxyd/integration_tests/rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/rate_limit_test.go @@ -29,7 +29,7 @@ func TestBackendMaxRPSLimit(t *testing.T) { config := ReadConfig("backend_rate_limit") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() limitedRes, codes := spamReqs(t, client, ethChainID, 503, 3) @@ -45,7 +45,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) config := ReadConfig("frontend_rate_limit") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/proxyd/proxyd/integration_tests/sender_rate_limit_test.go b/proxyd/proxyd/integration_tests/sender_rate_limit_test.go index b8a7730..a31a077 100644 --- a/proxyd/proxyd/integration_tests/sender_rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/sender_rate_limit_test.go @@ -43,7 +43,7 @@ func TestSenderRateLimitValidation(t *testing.T) { // validation. config.SenderRateLimit.Limit = math.MaxInt client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -73,7 +73,7 @@ func TestSenderRateLimitLimiting(t *testing.T) { config := ReadConfig("sender_rate_limit") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/proxyd/proxyd/integration_tests/testdata/consensus.toml b/proxyd/proxyd/integration_tests/testdata/consensus.toml new file mode 100644 index 0000000..dbd8e26 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/consensus.toml @@ -0,0 +1,24 @@ +[server] +rpc_port = 8080 + +[backend] +response_timeout_seconds = 1 + +[backends] +[backends.node1] +rpc_url = "$NODE1_URL" + +[backends.node2] +rpc_url = "$NODE2_URL" + +[backend_groups] +[backend_groups.node] +backends = ["node1", "node2"] +consensus_aware = true +consensus_handler = "noop" # allow more control over the consensus poller for tests + +[rpc_method_mappings] +eth_call = "node" +eth_chainId = "node" +eth_blockNumber = "node" +eth_getBlockByNumber = "node" diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml new file mode 100644 index 0000000..9d4f2d1 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml @@ -0,0 +1,44 @@ +- method: eth_getBlockByNumber + block: latest + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash1", + "number": "0x1" + } + } +- method: eth_getBlockByNumber + block: 0x1 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash1", + "number": "0x1" + } + } +- method: eth_getBlockByNumber + block: 0x2 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash2", + "number": "0x2" + } + } +- method: eth_getBlockByNumber + block: 0x3 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash3", + "number": "0x3" + } + } diff --git a/proxyd/proxyd/integration_tests/validation_test.go b/proxyd/proxyd/integration_tests/validation_test.go index f09e2c2..fed2c8a 100644 --- a/proxyd/proxyd/integration_tests/validation_test.go +++ b/proxyd/proxyd/integration_tests/validation_test.go @@ -26,7 +26,7 @@ func TestSingleRPCValidation(t *testing.T) { config := ReadConfig("whitelist") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -110,7 +110,7 @@ func TestBatchRPCValidation(t *testing.T) { config := ReadConfig("whitelist") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go index 9ae12ab..ed33779 100644 --- a/proxyd/proxyd/integration_tests/ws_test.go +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -38,7 +38,7 @@ func TestConcurrentWSPanic(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) config := ReadConfig("ws") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) client, err := NewProxydWSClient("ws://127.0.0.1:8546", nil, nil) require.NoError(t, err) @@ -147,7 +147,7 @@ func TestWS(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) config := ReadConfig("ws") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { clientHdlr.MsgCB(msgType, data) @@ -238,7 +238,7 @@ func TestWSClientClosure(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) config := ReadConfig("ws") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -278,7 +278,7 @@ func TestWSClientMaxConns(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) config := ReadConfig("ws") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 06fef15..656a8b6 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -242,6 +242,20 @@ var ( Name: "rate_limit_take_errors", Help: "Count of errors taking frontend rate limits", }) + + consensusLatestBlock = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "consensus_latest_block", + Help: "Consensus latest block", + }) + + backendLatestBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_latest_block", + Help: "Current latest block observed per backend", + }, []string{ + "backend_name", + }) ) func RecordRedisError(source string) { diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index ccc6897..5a34fa6 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -18,20 +18,20 @@ import ( "golang.org/x/sync/semaphore" ) -func Start(config *Config) (func(), error) { +func Start(config *Config) (*Server, func(), error) { if len(config.Backends) == 0 { - return nil, errors.New("must define at least one backend") + return nil, nil, errors.New("must define at least one backend") } if len(config.BackendGroups) == 0 { - return nil, errors.New("must define at least one backend group") + return nil, nil, errors.New("must define at least one backend group") } if len(config.RPCMethodMappings) == 0 { - return nil, errors.New("must define at least one RPC method mapping") + return nil, nil, errors.New("must define at least one RPC method mapping") } for authKey := range config.Authentication { if authKey == "none" { - return nil, errors.New("cannot use none as an auth key") + return nil, nil, errors.New("cannot use none as an auth key") } } @@ -39,16 +39,16 @@ func Start(config *Config) (func(), error) { if config.Redis.URL != "" { rURL, err := ReadFromEnvOrConfig(config.Redis.URL) if err != nil { - return nil, err + return nil, nil, err } redisClient, err = NewRedisClient(rURL) if err != nil { - return nil, err + return nil, nil, err } } if redisClient == nil && config.RateLimit.UseRedis { - return nil, errors.New("must specify a Redis URL if UseRedis is true in rate limit config") + return nil, nil, errors.New("must specify a Redis URL if UseRedis is true in rate limit config") } var lim BackendRateLimiter @@ -80,10 +80,10 @@ func Start(config *Config) (func(), error) { if config.SenderRateLimit.Enabled { if config.SenderRateLimit.Limit <= 0 { - return nil, errors.New("limit in sender_rate_limit must be > 0") + return nil, nil, errors.New("limit in sender_rate_limit must be > 0") } if time.Duration(config.SenderRateLimit.Interval) < time.Second { - return nil, errors.New("interval in sender_rate_limit must be >= 1s") + return nil, nil, errors.New("interval in sender_rate_limit must be >= 1s") } } @@ -100,17 +100,14 @@ func Start(config *Config) (func(), error) { rpcURL, err := ReadFromEnvOrConfig(cfg.RPCURL) if err != nil { - return nil, err + return nil, nil, err } wsURL, err := ReadFromEnvOrConfig(cfg.WSURL) if err != nil { - return nil, err + return nil, nil, err } if rpcURL == "" { - return nil, fmt.Errorf("must define an RPC URL for backend %s", name) - } - if wsURL == "" { - return nil, fmt.Errorf("must define a WS URL for backend %s", name) + return nil, nil, fmt.Errorf("must define an RPC URL for backend %s", name) } if config.BackendOptions.ResponseTimeoutSeconds != 0 { @@ -135,13 +132,13 @@ func Start(config *Config) (func(), error) { if cfg.Password != "" { passwordVal, err := ReadFromEnvOrConfig(cfg.Password) if err != nil { - return nil, err + return nil, nil, err } opts = append(opts, WithBasicAuth(cfg.Username, passwordVal)) } tlsConfig, err := configureBackendTLS(cfg) if err != nil { - return nil, err + return nil, nil, err } if tlsConfig != nil { log.Info("using custom TLS config for backend", "name", name) @@ -162,7 +159,7 @@ func Start(config *Config) (func(), error) { backends := make([]*Backend, 0) for _, bName := range bg.Backends { if backendsByName[bName] == nil { - return nil, fmt.Errorf("backend %s is not defined", bName) + return nil, nil, fmt.Errorf("backend %s is not defined", bName) } backends = append(backends, backendsByName[bName]) } @@ -177,17 +174,17 @@ func Start(config *Config) (func(), error) { if config.WSBackendGroup != "" { wsBackendGroup = backendGroups[config.WSBackendGroup] if wsBackendGroup == nil { - return nil, fmt.Errorf("ws backend group %s does not exist", config.WSBackendGroup) + return nil, nil, fmt.Errorf("ws backend group %s does not exist", config.WSBackendGroup) } } if wsBackendGroup == nil && config.Server.WSPort != 0 { - return nil, fmt.Errorf("a ws port was defined, but no ws group was defined") + return nil, nil, fmt.Errorf("a ws port was defined, but no ws group was defined") } for _, bg := range config.RPCMethodMappings { if backendGroups[bg] == nil { - return nil, fmt.Errorf("undefined backend group %s", bg) + return nil, nil, fmt.Errorf("undefined backend group %s", bg) } } @@ -198,7 +195,7 @@ func Start(config *Config) (func(), error) { for secret, alias := range config.Authentication { resolvedSecret, err := ReadFromEnvOrConfig(secret) if err != nil { - return nil, err + return nil, nil, err } resolvedAuth[resolvedSecret] = alias } @@ -217,11 +214,11 @@ func Start(config *Config) (func(), error) { ) if config.Cache.BlockSyncRPCURL == "" { - return nil, fmt.Errorf("block sync node required for caching") + return nil, nil, fmt.Errorf("block sync node required for caching") } blockSyncRPCURL, err := ReadFromEnvOrConfig(config.Cache.BlockSyncRPCURL) if err != nil { - return nil, err + return nil, nil, err } if redisClient == nil { @@ -233,7 +230,7 @@ func Start(config *Config) (func(), error) { // Ideally, the BlocKSyncRPCURL should be the sequencer or a HA replica that's not far behind ethClient, err := ethclient.Dial(blockSyncRPCURL) if err != nil { - return nil, err + return nil, nil, err } defer ethClient.Close() @@ -260,7 +257,7 @@ func Start(config *Config) (func(), error) { redisClient, ) if err != nil { - return nil, fmt.Errorf("error creating server: %w", err) + return nil, nil, fmt.Errorf("error creating server: %w", err) } if config.Metrics.Enabled { @@ -300,12 +297,28 @@ func Start(config *Config) (func(), error) { log.Crit("error starting WS server", "err", err) } }() + } else { + log.Info("WS server not enabled (ws_port is set to 0)") + } + + for bgName, bg := range backendGroups { + if config.BackendGroups[bgName].ConsensusAware { + log.Info("creating poller for consensus aware backend_group", "name", bgName) + + copts := make([]ConsensusOpt, 0) + + if config.BackendGroups[bgName].ConsensusAsyncHandler == "noop" { + copts = append(copts, WithAsyncHandler(NewNoopAsyncHandler())) + } + cp := NewConsensusPoller(bg, copts...) + bg.Consensus = cp + } } <-errTimer.C log.Info("started proxyd") - return func() { + shutdownFunc := func() { log.Info("shutting down proxyd") if blockNumLVC != nil { blockNumLVC.Stop() @@ -318,7 +331,9 @@ func Start(config *Config) (func(), error) { log.Error("error flushing backend ws conns", "err", err) } log.Info("goodbye") - }, nil + } + + return srv, shutdownFunc, nil } func secondsToDuration(seconds int) time.Duration { diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 5df9d37..fac04f0 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -39,7 +39,7 @@ const ( var emptyArrayResponse = json.RawMessage("[]") type Server struct { - backendGroups map[string]*BackendGroup + BackendGroups map[string]*BackendGroup wsBackendGroup *BackendGroup wsMethodWhitelist *StringSet rpcMethodMappings map[string]string @@ -152,7 +152,7 @@ func NewServer( } return &Server{ - backendGroups: backendGroups, + BackendGroups: backendGroups, wsBackendGroup: wsBackendGroup, wsMethodWhitelist: wsMethodWhitelist, rpcMethodMappings: rpcMethodMappings, @@ -476,7 +476,7 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL start := i * s.maxUpstreamBatchSize end := int(math.Min(float64(start+s.maxUpstreamBatchSize), float64(len(cacheMisses)))) elems := cacheMisses[start:end] - res, err := s.backendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) + res, err := s.BackendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) if err != nil { log.Error( "error forwarding RPC batch", @@ -559,7 +559,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context } ctx := context.WithValue(r.Context(), ContextKeyXForwardedFor, xff) // nolint:staticcheck - if s.authenticatedPaths == nil { + if len(s.authenticatedPaths) == 0 { // handle the edge case where auth is disabled // but someone sends in an auth key anyway if authorization != "" { diff --git a/proxyd/proxyd/tools/mockserver/handler/handler.go b/proxyd/proxyd/tools/mockserver/handler/handler.go new file mode 100644 index 0000000..18d6026 --- /dev/null +++ b/proxyd/proxyd/tools/mockserver/handler/handler.go @@ -0,0 +1,102 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + + "github.com/gorilla/mux" + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +type MethodTemplate struct { + Method string `yaml:"method"` + Block string `yaml:"block"` + Response string `yaml:"response"` +} + +type MockedHandler struct { + Overrides []*MethodTemplate + Autoload bool + AutoloadFile string +} + +func (mh *MockedHandler) Serve(port int) error { + r := mux.NewRouter() + r.HandleFunc("/", mh.Handler) + http.Handle("/", r) + fmt.Printf("starting server up on :%d serving MockedResponsesFile %s\n", port, mh.AutoloadFile) + err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil) + + if errors.Is(err, http.ErrServerClosed) { + fmt.Printf("server closed\n") + } else if err != nil { + fmt.Printf("error starting server: %s\n", err) + return err + } + return nil +} + +func (mh *MockedHandler) Handler(w http.ResponseWriter, req *http.Request) { + body, err := io.ReadAll(req.Body) + if err != nil { + fmt.Printf("error reading request: %v\n", err) + } + + var j map[string]interface{} + err = json.Unmarshal(body, &j) + if err != nil { + fmt.Printf("error reading request: %v\n", err) + } + + var template []*MethodTemplate + if mh.Autoload { + template = append(template, mh.LoadFromFile(mh.AutoloadFile)...) + } + if mh.Overrides != nil { + template = append(template, mh.Overrides...) + } + + method := j["method"] + block := "" + if method == "eth_getBlockByNumber" { + block = (j["params"].([]interface{})[0]).(string) + } + + var selectedResponse *string + for _, r := range template { + if r.Method == method && r.Block == block { + selectedResponse = &r.Response + } + } + if selectedResponse != nil { + _, err := fmt.Fprintf(w, *selectedResponse) + if err != nil { + fmt.Printf("error writing response: %v\n", err) + } + } +} + +func (mh *MockedHandler) LoadFromFile(file string) []*MethodTemplate { + contents, err := os.ReadFile(file) + if err != nil { + fmt.Printf("error reading MockedResponsesFile: %v\n", err) + } + var template []*MethodTemplate + err = yaml.Unmarshal(contents, &template) + if err != nil { + fmt.Printf("error reading MockedResponsesFile: %v\n", err) + } + return template +} + +func (mh *MockedHandler) AddOverride(template *MethodTemplate) { + mh.Overrides = append(mh.Overrides, template) +} + +func (mh *MockedHandler) ResetOverrides() { + mh.Overrides = make([]*MethodTemplate, 0) +} diff --git a/proxyd/proxyd/tools/mockserver/main.go b/proxyd/proxyd/tools/mockserver/main.go new file mode 100644 index 0000000..a58fc06 --- /dev/null +++ b/proxyd/proxyd/tools/mockserver/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "os" + "path" + "strconv" + + "github.com/ethereum-optimism/optimism/proxyd/tools/mockserver/handler" +) + +func main() { + if len(os.Args) < 3 { + fmt.Printf("simply mock a response based on an external text MockedResponsesFile\n") + fmt.Printf("usage: mockserver \n") + os.Exit(1) + } + port, _ := strconv.ParseInt(os.Args[1], 10, 32) + dir, _ := os.Getwd() + + h := handler.MockedHandler{ + Autoload: true, + AutoloadFile: path.Join(dir, os.Args[2]), + } + + err := h.Serve(int(port)) + if err != nil { + fmt.Printf("error starting mockserver: %v\n", err) + } +} diff --git a/proxyd/proxyd/tools/mockserver/node1.yml b/proxyd/proxyd/tools/mockserver/node1.yml new file mode 100644 index 0000000..c14f328 --- /dev/null +++ b/proxyd/proxyd/tools/mockserver/node1.yml @@ -0,0 +1,44 @@ +- method: eth_getBlockByNumber + block: latest + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash2", + "number": "0x2" + } + } +- method: eth_getBlockByNumber + block: 0x1 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash1", + "number": "0x1" + } + } +- method: eth_getBlockByNumber + block: 0x2 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash2", + "number": "0x2" + } + } +- method: eth_getBlockByNumber + block: 0x3 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash34", + "number": "0x3" + } + } \ No newline at end of file diff --git a/proxyd/proxyd/tools/mockserver/node2.yml b/proxyd/proxyd/tools/mockserver/node2.yml new file mode 100644 index 0000000..b94ee7a --- /dev/null +++ b/proxyd/proxyd/tools/mockserver/node2.yml @@ -0,0 +1,44 @@ +- method: eth_getBlockByNumber + block: latest + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash2", + "number": "0x2" + } + } +- method: eth_getBlockByNumber + block: 0x1 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash1", + "number": "0x1" + } + } +- method: eth_getBlockByNumber + block: 0x2 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash2", + "number": "0x2" + } + } +- method: eth_getBlockByNumber + block: 0x3 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash3", + "number": "0x3" + } + } \ No newline at end of file From 1686d06e5a0831a287cb6581b9e310af28d09a31 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 21 Apr 2023 10:36:42 -0700 Subject: [PATCH 050/212] addressing final comments --- proxyd/proxyd/consensus_poller.go | 47 ++++-------- proxyd/proxyd/consensus_poller_test.go | 74 ------------------- proxyd/proxyd/consensus_tracker.go | 20 ++--- .../integration_tests/consensus_test.go | 28 +++---- proxyd/proxyd/metrics.go | 16 +++- 5 files changed, 55 insertions(+), 130 deletions(-) delete mode 100644 proxyd/proxyd/consensus_poller_test.go diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 81f8fc3..d67969d 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -34,7 +34,7 @@ type ConsensusPoller struct { type backendState struct { backendStateMux sync.Mutex - latestBlockNumber string + latestBlockNumber hexutil.Uint64 latestBlockHash string lastUpdate time.Time @@ -54,7 +54,7 @@ func (cp *ConsensusPoller) GetConsensusGroup() []*Backend { } // GetConsensusBlockNumber returns the agreed block number in a consensus -func (ct *ConsensusPoller) GetConsensusBlockNumber() string { +func (ct *ConsensusPoller) GetConsensusBlockNumber() hexutil.Uint64 { return ct.tracker.GetConsensusBlockNumber() } @@ -198,28 +198,28 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { changed := cp.setBackendState(be, latestBlockNumber, latestBlockHash) if changed { - backendLatestBlockBackend.WithLabelValues(be.Name).Set(blockToFloat(latestBlockNumber)) + RecordBackendLatestBlock(be, latestBlockNumber) log.Info("backend state updated", "name", be.Name, "state", bs) } } // UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - var lowestBlock string + var lowestBlock hexutil.Uint64 var lowestBlockHash string currentConsensusBlockNumber := cp.GetConsensusBlockNumber() for _, be := range cp.backendGroup.Backends { backendLatestBlockNumber, backendLatestBlockHash := cp.getBackendState(be) - if lowestBlock == "" || backendLatestBlockNumber < lowestBlock { + if lowestBlock == 0 || backendLatestBlockNumber < lowestBlock { lowestBlock = backendLatestBlockNumber lowestBlockHash = backendLatestBlockHash } } // no block to propose (i.e. initializing consensus) - if lowestBlock == "" { + if lowestBlock == 0 { return } @@ -247,7 +247,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { continue } - actualBlockNumber, actualBlockHash, err := cp.fetchBlock(ctx, be, proposedBlock) + actualBlockNumber, actualBlockHash, err := cp.fetchBlock(ctx, be, proposedBlock.String()) if err != nil { log.Warn("error updating backend", "name", be.Name, "err", err) continue @@ -257,7 +257,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { } blocksDontMatch := (actualBlockNumber != proposedBlock) || (actualBlockHash != proposedBlockHash) if blocksDontMatch { - if blockAheadOrEqual(currentConsensusBlockNumber, actualBlockNumber) { + if currentConsensusBlockNumber >= actualBlockNumber { log.Warn("backend broke consensus", "name", be.Name, "blockNum", actualBlockNumber, "proposedBlockNum", proposedBlock, "blockHash", actualBlockHash, "proposedBlockHash", proposedBlockHash) broken = true } @@ -271,7 +271,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { hasConsensus = true } else { // walk one block behind and try again - proposedBlock = hexAdd(proposedBlock, -1) + proposedBlock -= 1 proposedBlockHash = "" log.Info("no consensus, now trying", "block:", proposedBlock) } @@ -283,7 +283,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { } cp.tracker.SetConsensusBlockNumber(proposedBlock) - consensusLatestBlock.Set(blockToFloat(proposedBlock)) + RecordGroupConsensusLatestBlock(cp.backendGroup, proposedBlock) cp.consensusGroupMux.Lock() cp.consensusGroup = consensusBackends cp.consensusGroupMux.Unlock() @@ -292,24 +292,24 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { } // fetchBlock Convenient wrapper to make a request to get a block directly from the backend -func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block string) (blockNumber string, blockHash string, err error) { +func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block string) (blockNumber hexutil.Uint64, blockHash string, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_getBlockByNumber", block, false) if err != nil { - return "", "", err + return 0, "", err } jsonMap, ok := rpcRes.Result.(map[string]interface{}) if !ok { - return "", "", fmt.Errorf(fmt.Sprintf("unexpected response type checking consensus on backend %s", be.Name)) + return 0, "", fmt.Errorf("unexpected response type checking consensus on backend %s", be.Name) } - blockNumber = jsonMap["number"].(string) + blockNumber = hexutil.Uint64(hexutil.MustDecodeUint64(jsonMap["number"].(string))) blockHash = jsonMap["hash"].(string) return } -func (cp *ConsensusPoller) getBackendState(be *Backend) (blockNumber string, blockHash string) { +func (cp *ConsensusPoller) getBackendState(be *Backend) (blockNumber hexutil.Uint64, blockHash string) { bs := cp.backendState[be] bs.backendStateMux.Lock() blockNumber = bs.latestBlockNumber @@ -318,7 +318,7 @@ func (cp *ConsensusPoller) getBackendState(be *Backend) (blockNumber string, blo return } -func (cp *ConsensusPoller) setBackendState(be *Backend, blockNumber string, blockHash string) (changed bool) { +func (cp *ConsensusPoller) setBackendState(be *Backend, blockNumber hexutil.Uint64, blockHash string) (changed bool) { bs := cp.backendState[be] bs.backendStateMux.Lock() changed = bs.latestBlockHash != blockHash @@ -328,18 +328,3 @@ func (cp *ConsensusPoller) setBackendState(be *Backend, blockNumber string, bloc bs.backendStateMux.Unlock() return } - -// hexAdd Convenient way to convert hex block to uint64, increment, and convert back to hex -func hexAdd(hexVal string, incr int64) string { - return hexutil.EncodeUint64(uint64(int64(hexutil.MustDecodeUint64(hexVal)) + incr)) -} - -// blockAheadOrEqual Convenient way to check if `baseBlock` is ahead or equal than `checkBlock` -func blockAheadOrEqual(baseBlock string, checkBlock string) bool { - return hexutil.MustDecodeUint64(baseBlock) >= hexutil.MustDecodeUint64(checkBlock) -} - -// blockToFloat Convenient way to convert a hex block to float64 -func blockToFloat(hexVal string) float64 { - return float64(hexutil.MustDecodeUint64(hexVal)) -} diff --git a/proxyd/proxyd/consensus_poller_test.go b/proxyd/proxyd/consensus_poller_test.go deleted file mode 100644 index 06680fa..0000000 --- a/proxyd/proxyd/consensus_poller_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package proxyd - -import ( - "testing" -) - -func Test_blockToFloat(t *testing.T) { - type args struct { - hexVal string - } - tests := []struct { - name string - args args - want float64 - }{ - {"0xf1b3", args{"0xf1b3"}, float64(61875)}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := blockToFloat(tt.args.hexVal); got != tt.want { - t.Errorf("blockToFloat() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_hexAdd(t *testing.T) { - type args struct { - hexVal string - incr int64 - } - tests := []struct { - name string - args args - want string - }{ - {"0x1", args{"0x1", 1}, "0x2"}, - {"0x2", args{"0x2", -1}, "0x1"}, - {"0xf", args{"0xf", 1}, "0x10"}, - {"0x10", args{"0x10", -1}, "0xf"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := hexAdd(tt.args.hexVal, tt.args.incr); got != tt.want { - t.Errorf("hexAdd() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_blockAheadOrEqual(t *testing.T) { - type args struct { - baseBlock string - checkBlock string - } - tests := []struct { - name string - args args - want bool - }{ - {"0x1 vs 0x1", args{"0x1", "0x1"}, true}, - {"0x2 vs 0x1", args{"0x2", "0x1"}, true}, - {"0x1 vs 0x2", args{"0x1", "0x2"}, false}, - {"0xff vs 0x100", args{"0xff", "0x100"}, false}, - {"0x100 vs 0xff", args{"0x100", "0xff"}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := blockAheadOrEqual(tt.args.baseBlock, tt.args.checkBlock); got != tt.want { - t.Errorf("blockAheadOrEqual() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go index 7218b32..3bd10e7 100644 --- a/proxyd/proxyd/consensus_tracker.go +++ b/proxyd/proxyd/consensus_tracker.go @@ -5,37 +5,39 @@ import ( "fmt" "sync" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/go-redis/redis/v8" ) // ConsensusTracker abstracts how we store and retrieve the current consensus // allowing it to be stored locally in-memory or in a shared Redis cluster type ConsensusTracker interface { - GetConsensusBlockNumber() string - SetConsensusBlockNumber(blockNumber string) + GetConsensusBlockNumber() hexutil.Uint64 + SetConsensusBlockNumber(blockNumber hexutil.Uint64) } // InMemoryConsensusTracker store and retrieve in memory, async-safe type InMemoryConsensusTracker struct { - consensusBlockNumber string + consensusBlockNumber hexutil.Uint64 mutex sync.Mutex } func NewInMemoryConsensusTracker() ConsensusTracker { return &InMemoryConsensusTracker{ - consensusBlockNumber: "", // empty string semantics means unknown + consensusBlockNumber: 0, mutex: sync.Mutex{}, } } -func (ct *InMemoryConsensusTracker) GetConsensusBlockNumber() string { +func (ct *InMemoryConsensusTracker) GetConsensusBlockNumber() hexutil.Uint64 { defer ct.mutex.Unlock() ct.mutex.Lock() return ct.consensusBlockNumber } -func (ct *InMemoryConsensusTracker) SetConsensusBlockNumber(blockNumber string) { +func (ct *InMemoryConsensusTracker) SetConsensusBlockNumber(blockNumber hexutil.Uint64) { defer ct.mutex.Unlock() ct.mutex.Lock() @@ -61,10 +63,10 @@ func (ct *RedisConsensusTracker) key() string { return fmt.Sprintf("consensus_latest_block:%s", ct.backendGroup) } -func (ct *RedisConsensusTracker) GetConsensusBlockNumber() string { - return ct.client.Get(ct.ctx, ct.key()).Val() +func (ct *RedisConsensusTracker) GetConsensusBlockNumber() hexutil.Uint64 { + return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key()).Val())) } -func (ct *RedisConsensusTracker) SetConsensusBlockNumber(blockNumber string) { +func (ct *RedisConsensusTracker) SetConsensusBlockNumber(blockNumber hexutil.Uint64) { ct.client.Set(ct.ctx, ct.key(), blockNumber, 0) } diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 1ed7d12..da085bf 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -56,7 +56,7 @@ func TestConsensus(t *testing.T) { h2.ResetOverrides() // unknown consensus at init - require.Equal(t, "", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x0", bg.Consensus.GetConsensusBlockNumber().String()) // first poll for _, be := range bg.Backends { @@ -65,7 +65,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // consensus at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) }) t.Run("advance consensus", func(t *testing.T) { @@ -78,7 +78,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) // advance latest on node2 to 0x2 h2.AddOverride(&ms.MethodTemplate{ @@ -95,7 +95,7 @@ func TestConsensus(t *testing.T) { // consensus should stick to 0x1, since node1 is still lagging there bg.Consensus.UpdateBackendGroupConsensus(ctx) - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) // advance latest on node1 to 0x2 h1.AddOverride(&ms.MethodTemplate{ @@ -111,7 +111,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // should stick to 0x2, since now all nodes are at 0x2 - require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber().String()) }) t.Run("broken consensus", func(t *testing.T) { @@ -124,7 +124,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) // advance latest on both nodes to 0x2 h1.AddOverride(&ms.MethodTemplate{ @@ -145,7 +145,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // at 0x2 - require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber().String()) // make node2 diverge on hash h2.AddOverride(&ms.MethodTemplate{ @@ -161,7 +161,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // should resolve to 0x1, since 0x2 is out of consensus at the moment - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) // later, when impl events, listen to broken consensus event }) @@ -176,7 +176,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) // advance latest on both nodes to 0x2 h1.AddOverride(&ms.MethodTemplate{ @@ -197,7 +197,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // at 0x2 - require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber().String()) // advance latest on both nodes to 0x3 h1.AddOverride(&ms.MethodTemplate{ @@ -218,7 +218,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // at 0x3 - require.Equal(t, "0x3", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x3", bg.Consensus.GetConsensusBlockNumber().String()) // make node2 diverge on hash for blocks 0x2 and 0x3 h2.AddOverride(&ms.MethodTemplate{ @@ -239,7 +239,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // should resolve to 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) }) t.Run("fork in advanced block", func(t *testing.T) { @@ -252,7 +252,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) // make nodes 1 and 2 advance in forks h1.AddOverride(&ms.MethodTemplate{ @@ -293,7 +293,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // should resolve to 0x1, the highest common ancestor - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber()) + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) }) } diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 656a8b6..2aef49d 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -5,6 +5,8 @@ import ( "strconv" "strings" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) @@ -243,10 +245,12 @@ var ( Help: "Count of errors taking frontend rate limits", }) - consensusLatestBlock = promauto.NewGauge(prometheus.GaugeOpts{ + consensusLatestBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, - Name: "consensus_latest_block", + Name: "group_consensus_latest_block", Help: "Consensus latest block", + }, []string{ + "backend_group_name", }) backendLatestBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ @@ -316,3 +320,11 @@ func RecordCacheMiss(method string) { func RecordBatchSize(size int) { batchSizeHistogram.Observe(float64(size)) } + +func RecordBackendLatestBlock(be *Backend, blockNumber hexutil.Uint64) { + backendLatestBlockBackend.WithLabelValues(be.Name).Set(float64(blockNumber)) +} + +func RecordGroupConsensusLatestBlock(group *BackendGroup, blockNumber hexutil.Uint64) { + consensusLatestBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) +} From a61c8f3f2af32b34ac364c5e4cc68db899d966a9 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 24 Apr 2023 13:14:21 -0700 Subject: [PATCH 051/212] sliding window pkg --- proxyd/proxyd/go.mod | 1 + proxyd/proxyd/go.sum | 2 + .../proxyd/pkg/avg-sliding-window/sliding.go | 176 +++++++++++ .../pkg/avg-sliding-window/sliding_test.go | 278 ++++++++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 proxyd/proxyd/pkg/avg-sliding-window/sliding.go create mode 100644 proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 68bf63c..97c7b02 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -31,6 +31,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/fjl/memsize v0.0.1 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/go-ole/go-ole v1.2.6 // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 4db2ccf..4c71d15 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -140,6 +140,8 @@ github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7j github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 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= diff --git a/proxyd/proxyd/pkg/avg-sliding-window/sliding.go b/proxyd/proxyd/pkg/avg-sliding-window/sliding.go new file mode 100644 index 0000000..721daba --- /dev/null +++ b/proxyd/proxyd/pkg/avg-sliding-window/sliding.go @@ -0,0 +1,176 @@ +package avg_sliding_window + +import ( + "time" + + lm "github.com/emirpasic/gods/maps/linkedhashmap" +) + +type Clock interface { + Now() time.Time +} + +// DefaultClock provides a clock that gets current time from the system time +type DefaultClock struct{} + +func NewDefaultClock() *DefaultClock { + return &DefaultClock{} +} +func (c DefaultClock) Now() time.Time { + return time.Now() +} + +// AdjustableClock provides a static clock to easily override the system time +type AdjustableClock struct { + now time.Time +} + +func NewAdjustableClock(now time.Time) *AdjustableClock { + return &AdjustableClock{now: now} +} +func (c *AdjustableClock) Now() time.Time { + return c.now +} +func (c *AdjustableClock) Set(now time.Time) { + c.now = now +} + +type bucket struct { + sum float64 + qty uint +} + +// AvgSlidingWindow calculates moving averages efficiently. +// Data points are rounded to nearest bucket of size `bucketSize`, +// and evicted when they are too old based on `windowLength` +type AvgSlidingWindow struct { + bucketSize time.Duration + windowLength time.Duration + clock Clock + buckets *lm.Map + qty uint + sum float64 +} + +type SlidingWindowOpts func(sw *AvgSlidingWindow) + +func NewSlidingWindow(opts ...SlidingWindowOpts) *AvgSlidingWindow { + sw := &AvgSlidingWindow{ + buckets: lm.New(), + } + for _, opt := range opts { + opt(sw) + } + if sw.bucketSize == 0 { + sw.bucketSize = time.Second + } + if sw.windowLength == 0 { + sw.windowLength = 5 * time.Minute + } + if sw.clock == nil { + sw.clock = NewDefaultClock() + } + return sw +} + +func WithWindowLength(windowLength time.Duration) SlidingWindowOpts { + return func(sw *AvgSlidingWindow) { + sw.windowLength = windowLength + } +} + +func WithBucketSize(bucketSize time.Duration) SlidingWindowOpts { + return func(sw *AvgSlidingWindow) { + sw.bucketSize = bucketSize + } +} + +func WithClock(clock Clock) SlidingWindowOpts { + return func(sw *AvgSlidingWindow) { + sw.clock = clock + } +} + +func (sw *AvgSlidingWindow) inWindow(t time.Time) bool { + now := sw.clock.Now().Round(sw.bucketSize) + windowStart := now.Add(-sw.windowLength) + return windowStart.Before(t) && !t.After(now) +} + +// Add inserts a new data point into the window, with value `val` with the current time +func (sw *AvgSlidingWindow) Add(val float64) { + t := sw.clock.Now() + sw.AddWithTime(t, val) +} + +// AddWithTime inserts a new data point into the window, with value `val` and time `t` +func (sw *AvgSlidingWindow) AddWithTime(t time.Time, val float64) { + sw.advance() + + key := t.Round(sw.bucketSize) + if !sw.inWindow(key) { + return + } + + var b *bucket + current, found := sw.buckets.Get(key) + if !found { + b = &bucket{} + } else { + b = current.(*bucket) + } + + // update bucket + bsum := b.sum + b.qty += 1 + b.sum = bsum + val + + // update window + wsum := sw.sum + sw.qty += 1 + sw.sum = wsum - bsum + b.sum + sw.buckets.Put(key, b) +} + +// advance evicts old data points +func (sw *AvgSlidingWindow) advance() { + now := sw.clock.Now().Round(sw.bucketSize) + windowStart := now.Add(-sw.windowLength) + keys := sw.buckets.Keys() + for _, k := range keys { + if k.(time.Time).After(windowStart) { + break + } + val, _ := sw.buckets.Get(k) + b := val.(*bucket) + sw.buckets.Remove(k) + if sw.buckets.Size() > 0 { + sw.qty -= b.qty + sw.sum = sw.sum - b.sum + } else { + sw.qty = 0 + sw.sum = 0.0 + } + } +} + +// Avg retrieves the current average for the sliding window +func (sw *AvgSlidingWindow) Avg() float64 { + sw.advance() + if sw.qty == 0 { + return 0 + } + return sw.sum / float64(sw.qty) +} + +// Sum retrieves the current sum for the sliding window +func (sw *AvgSlidingWindow) Sum() float64 { + sw.advance() + return sw.sum +} + +// Count retrieves the data point count for the sliding window +func (sw *AvgSlidingWindow) Count() uint { + sw.advance() + return sw.qty +} diff --git a/proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go b/proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go new file mode 100644 index 0000000..7f5e9b7 --- /dev/null +++ b/proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go @@ -0,0 +1,278 @@ +package avg_sliding_window + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestSlidingWindow_AddWithTime_Single(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 5.0, sw.Sum()) + require.Equal(t, 1, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[0].(*bucket).sum) +} + +func TestSlidingWindow_AddWithTime_TwoValues_SameBucket(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 10.0, sw.Sum()) + require.Equal(t, 2, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 2, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 10.0, sw.buckets.Values()[0].(*bucket).sum) +} + +func TestSlidingWindow_AddWithTime_ThreeValues_SameBucket(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 15.0, sw.Sum()) + require.Equal(t, 3, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 15.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 3, int(sw.buckets.Values()[0].(*bucket).qty)) +} + +func TestSlidingWindow_AddWithTime_ThreeValues_ThreeBuckets(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 15.0, sw.Sum()) + require.Equal(t, 3, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 4.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[2].(*bucket).sum) +} + +func TestSlidingWindow_AddWithTime_OutWindow(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:03:55"), 1000) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 15.0, sw.Sum()) + require.Equal(t, 3, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 4.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[2].(*bucket).sum) +} + +func TestSlidingWindow_AdvanceClock(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 15.0, sw.Sum()) + require.Equal(t, 3, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 4.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[2].(*bucket).sum) + + // up until 15:04:05 we had 3 buckets + // let's advance the clock to 15:04:11 and the first data point should be evicted + clock.Set(ts("2023-04-21 15:04:11")) + require.Equal(t, 5.5, sw.Avg()) + require.Equal(t, 11.0, sw.Sum()) + require.Equal(t, 2, int(sw.Count())) + require.Equal(t, 2, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[1].(*bucket).sum) + + // let's advance the clock to 15:04:12 and another data point should be evicted + clock.Set(ts("2023-04-21 15:04:12")) + require.Equal(t, 6.0, sw.Avg()) + require.Equal(t, 6.0, sw.Sum()) + require.Equal(t, 1, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[0].(*bucket).sum) + + // let's advance the clock to 15:04:25 and all data point should be evicted + clock.Set(ts("2023-04-21 15:04:25")) + require.Equal(t, 0.0, sw.Avg()) + require.Equal(t, 0.0, sw.Sum()) + require.Equal(t, 0, int(sw.Count())) + require.Equal(t, 0, sw.buckets.Size()) +} + +func TestSlidingWindow_MultipleValPerBucket(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 12) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 15) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 3) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 1) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 3) + require.Equal(t, 6.125, sw.Avg()) + require.Equal(t, 49.0, sw.Sum()) + require.Equal(t, 8, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + require.Equal(t, 2, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 16.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 2, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 20.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 4, int(sw.buckets.Values()[2].(*bucket).qty)) + require.Equal(t, 13.0, sw.buckets.Values()[2].(*bucket).sum) + + // up until 15:04:05 we had 3 buckets + // let's advance the clock to 15:04:11 and the first data point should be evicted + clock.Set(ts("2023-04-21 15:04:11")) + require.Equal(t, 5.5, sw.Avg()) + require.Equal(t, 33.0, sw.Sum()) + require.Equal(t, 6, int(sw.Count())) + require.Equal(t, 2, sw.buckets.Size()) + require.Equal(t, 2, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 20.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 4, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 13.0, sw.buckets.Values()[1].(*bucket).sum) + + // let's advance the clock to 15:04:12 and another data point should be evicted + clock.Set(ts("2023-04-21 15:04:12")) + require.Equal(t, 3.25, sw.Avg()) + require.Equal(t, 13.0, sw.Sum()) + require.Equal(t, 4, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 4, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 13.0, sw.buckets.Values()[0].(*bucket).sum) + + // let's advance the clock to 15:04:25 and all data point should be evicted + clock.Set(ts("2023-04-21 15:04:25")) + require.Equal(t, 0.0, sw.Avg()) + require.Equal(t, 0, sw.buckets.Size()) +} + +func TestSlidingWindow_CustomBucket(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(30*time.Second), + WithBucketSize(10*time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:03:49"), 5) // key: 03:50, sum: 5.0 + sw.AddWithTime(ts("2023-04-21 15:04:02"), 15) // key: 04:00 + sw.AddWithTime(ts("2023-04-21 15:04:03"), 5) // key: 04:00 + sw.AddWithTime(ts("2023-04-21 15:04:04"), 1) // key: 04:00, sum: 21.0 + sw.AddWithTime(ts("2023-04-21 15:04:05"), 3) // key: 04:10, sum: 3.0 + require.Equal(t, 5.8, sw.Avg()) + require.Equal(t, 29.0, sw.Sum()) + require.Equal(t, 5, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + require.Equal(t, 5.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 21.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 3, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 3.0, sw.buckets.Values()[2].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) + + // up until 15:04:05 we had 3 buckets + // let's advance the clock to 15:04:21 and the first data point should be evicted + clock.Set(ts("2023-04-21 15:04:21")) + require.Equal(t, 6.0, sw.Avg()) + require.Equal(t, 24.0, sw.Sum()) + require.Equal(t, 4, int(sw.Count())) + require.Equal(t, 2, sw.buckets.Size()) + require.Equal(t, 21.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 3, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 3.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + + // let's advance the clock to 15:04:32 and another data point should be evicted + clock.Set(ts("2023-04-21 15:04:32")) + require.Equal(t, 3.0, sw.Avg()) + require.Equal(t, 3.0, sw.Sum()) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 1, int(sw.Count())) + require.Equal(t, 3.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + + // let's advance the clock to 15:04:46 and all data point should be evicted + clock.Set(ts("2023-04-21 15:04:46")) + require.Equal(t, 0.0, sw.Avg()) + require.Equal(t, 0.0, sw.Sum()) + require.Equal(t, 0, int(sw.Count())) + require.Equal(t, 0, sw.buckets.Size()) +} + +// ts is a convenient method that must parse a time.Time from a string in format `"2006-01-02 15:04:05"` +func ts(s string) time.Time { + format := "2006-01-02 15:04:05" + t, err := time.Parse(format, s) + if err != nil { + panic(err) + } + return t +} From 26f7d10e160016e266cf9e334ac9d3af59cdd5f1 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 25 Apr 2023 10:26:55 -0700 Subject: [PATCH 052/212] proxyd: integrate health checks --- proxyd/proxyd/backend.go | 71 ++++++++- proxyd/proxyd/consensus_poller.go | 147 ++++++++++++++++-- .../integration_tests/consensus_test.go | 134 ++++++++++++---- .../testdata/consensus_responses.yml | 14 ++ .../proxyd/pkg/avg-sliding-window/sliding.go | 7 +- 5 files changed, 330 insertions(+), 43 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 5e8c632..80e75fd 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -17,6 +17,8 @@ import ( "sync" "time" + sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" + "github.com/ethereum/go-ethereum/log" "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus" @@ -83,6 +85,11 @@ var ( Message: "sender is over rate limit", HTTPErrorCode: 429, } + ErrNotHealthy = &RPCErr{ + Code: JSONRPCErrorInternal - 18, + Message: "backend is currently not healthy to serve traffic", + HTTPErrorCode: 429, + } ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") ) @@ -119,6 +126,14 @@ type Backend struct { outOfServiceInterval time.Duration stripTrailingXFF bool proxydIP string + + maxDegradedLatencyThreshold time.Duration + maxLatencyThreshold time.Duration + maxErrorRateThreshold float64 + + latencySlidingWindow *sw.AvgSlidingWindow + networkRequestsSlidingWindow *sw.AvgSlidingWindow + networkErrorsSlidingWindow *sw.AvgSlidingWindow } type BackendOpt func(b *Backend) @@ -187,6 +202,18 @@ func WithProxydIP(ip string) BackendOpt { } } +func WithMaxLatencyThreshold(maxLatencyThreshold time.Duration) BackendOpt { + return func(b *Backend) { + b.maxLatencyThreshold = maxLatencyThreshold + } +} + +func WithMaxErrorRateThreshold(maxErrorRateThreshold float64) BackendOpt { + return func(b *Backend) { + b.maxErrorRateThreshold = maxErrorRateThreshold + } +} + func NewBackend( name string, rpcURL string, @@ -207,6 +234,14 @@ func NewBackend( backendName: name, }, dialer: &websocket.Dialer{}, + + maxLatencyThreshold: 10 * time.Second, + maxDegradedLatencyThreshold: 5 * time.Second, + maxErrorRateThreshold: 0.5, + + latencySlidingWindow: sw.NewSlidingWindow(), + networkRequestsSlidingWindow: sw.NewSlidingWindow(), + networkErrorsSlidingWindow: sw.NewSlidingWindow(), } for _, opt := range opts { @@ -252,11 +287,11 @@ func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([] case nil: // do nothing // ErrBackendUnexpectedJSONRPC occurs because infura responds with a single JSON-RPC object // to a batch request whenever any Request Object in the batch would induce a partial error. - // We don't label the the backend offline in this case. But the error is still returned to + // We don't label the backend offline in this case. But the error is still returned to // callers so failover can occur if needed. case ErrBackendUnexpectedJSONRPC: log.Debug( - "Reecived unexpected JSON-RPC response", + "Received unexpected JSON-RPC response", "name", b.Name, "req_id", GetReqID(ctx), "err", err, @@ -396,6 +431,9 @@ func (b *Backend) ForwardRPC(ctx context.Context, res *RPCRes, id string, method } func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { + // we are concerned about network error rates, so we record 1 request independently of how many are in the batch + b.networkRequestsSlidingWindow.Incr() + isSingleElementBatch := len(rpcReqs) == 1 // Single element batches are unwrapped before being sent @@ -410,6 +448,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool httpReq, err := http.NewRequestWithContext(ctx, "POST", b.rpcURL, bytes.NewReader(body)) if err != nil { + b.networkErrorsSlidingWindow.Incr() return nil, wrapErr(err, "error creating backend request") } @@ -427,8 +466,10 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool httpReq.Header.Set("content-type", "application/json") httpReq.Header.Set("X-Forwarded-For", xForwardedFor) + start := time.Now() httpRes, err := b.client.DoLimited(httpReq) if err != nil { + b.networkErrorsSlidingWindow.Incr() return nil, wrapErr(err, "error in backend request") } @@ -446,12 +487,14 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // Alchemy returns a 400 on bad JSONs, so handle that case if httpRes.StatusCode != 200 && httpRes.StatusCode != 400 { + b.networkErrorsSlidingWindow.Incr() return nil, fmt.Errorf("response code %d", httpRes.StatusCode) } defer httpRes.Body.Close() resB, err := io.ReadAll(io.LimitReader(httpRes.Body, b.maxResponseSize)) if err != nil { + b.networkErrorsSlidingWindow.Incr() return nil, wrapErr(err, "error reading response body") } @@ -468,13 +511,16 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool if err := json.Unmarshal(resB, &res); err != nil { // Infura may return a single JSON-RPC response if, for example, the batch contains a request for an unsupported method if responseIsNotBatched(resB) { + b.networkErrorsSlidingWindow.Incr() return nil, ErrBackendUnexpectedJSONRPC } + b.networkErrorsSlidingWindow.Incr() return nil, ErrBackendBadResponse } } if len(rpcReqs) != len(res) { + b.networkErrorsSlidingWindow.Incr() return nil, ErrBackendUnexpectedJSONRPC } @@ -485,11 +531,32 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool res.Error.HTTPErrorCode = httpRes.StatusCode } } + duration := time.Since(start) + b.latencySlidingWindow.Add(float64(duration)) sortBatchRPCResponse(rpcReqs, res) return res, nil } +// IsHealthy checks if the backend is able to serve traffic, based on dynamic parameters +func (b *Backend) IsHealthy() bool { + errorRate := b.networkErrorsSlidingWindow.Sum() / b.networkRequestsSlidingWindow.Sum() + avgLatency := time.Duration(b.latencySlidingWindow.Avg()) + if errorRate >= b.maxErrorRateThreshold { + return false + } + if avgLatency >= b.maxLatencyThreshold { + return false + } + return true +} + +// IsDegraded checks if the backend is serving traffic in a degraded state (i.e. used as a last resource) +func (b *Backend) IsDegraded() bool { + avgLatency := time.Duration(b.latencySlidingWindow.Avg()) + return avgLatency >= b.maxDegradedLatencyThreshold +} + func responseIsNotBatched(b []byte) bool { var r RPCRes return json.Unmarshal(b, &r) == nil diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index d67969d..7d01c9b 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -3,6 +3,7 @@ package proxyd import ( "context" "fmt" + "strconv" "strings" "sync" "time" @@ -29,6 +30,11 @@ type ConsensusPoller struct { tracker ConsensusTracker asyncHandler ConsensusAsyncHandler + + minPeerCount uint64 + + banPeriod time.Duration + maxUpdateThreshold time.Duration } type backendState struct { @@ -36,6 +42,7 @@ type backendState struct { latestBlockNumber hexutil.Uint64 latestBlockHash string + peerCount uint64 lastUpdate time.Time @@ -47,7 +54,7 @@ func (cp *ConsensusPoller) GetConsensusGroup() []*Backend { defer cp.consensusGroupMux.Unlock() cp.consensusGroupMux.Lock() - g := make([]*Backend, len(cp.backendGroup.Backends)) + g := make([]*Backend, len(cp.consensusGroup)) copy(g, cp.consensusGroup) return g @@ -141,6 +148,24 @@ func WithAsyncHandler(asyncHandler ConsensusAsyncHandler) ConsensusOpt { } } +func WithBanPeriod(banPeriod time.Duration) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.banPeriod = banPeriod + } +} + +func WithMaxUpdateThreshold(maxUpdateThreshold time.Duration) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.maxUpdateThreshold = maxUpdateThreshold + } +} + +func WithMinPeerCount(minPeerCount uint64) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.minPeerCount = minPeerCount + } +} + func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller { ctx, cancelFunc := context.WithCancel(context.Background()) @@ -153,6 +178,10 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller cancelFunc: cancelFunc, backendGroup: bg, backendState: state, + + banPeriod: 5 * time.Minute, + maxUpdateThreshold: 30 * time.Second, + minPeerCount: 3, } for _, opt := range opts { @@ -180,14 +209,29 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - if be.IsRateLimited() || !be.Online() { + // if backend it not online or not in a health state we'll only resume checkin it after ban + if !be.Online() || !be.IsHealthy() { + log.Warn("backend banned - not online or not healthy", "backend", be.Name, "bannedUntil", bs.bannedUntil) + bs.bannedUntil = time.Now().Add(cp.banPeriod) + } + + // if backend it not in sync we'll check again after ban + inSync, err := cp.isInSync(ctx, be) + if err != nil || !inSync { + log.Warn("backend banned - not in sync", "backend", be.Name, "bannedUntil", bs.bannedUntil) + bs.bannedUntil = time.Now().Add(cp.banPeriod) + } + + // if backend exhausted rate limit we'll skip it for now + if be.IsRateLimited() { return } - // we'll introduce here checks to ban the backend - // i.e. node is syncing the chain - - // then update backend consensus + peerCount, err := cp.getPeerCount(ctx, be) + if err != nil { + log.Warn("error updating backend", "name", be.Name, "err", err) + return + } latestBlockNumber, latestBlockHash, err := cp.fetchBlock(ctx, be, "latest") if err != nil { @@ -195,7 +239,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - changed := cp.setBackendState(be, latestBlockNumber, latestBlockHash) + changed := cp.setBackendState(be, peerCount, latestBlockNumber, latestBlockHash) if changed { RecordBackendLatestBlock(be, latestBlockNumber) @@ -211,7 +255,15 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { currentConsensusBlockNumber := cp.GetConsensusBlockNumber() for _, be := range cp.backendGroup.Backends { - backendLatestBlockNumber, backendLatestBlockHash := cp.getBackendState(be) + peerCount, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate := cp.getBackendState(be) + + if peerCount < cp.minPeerCount { + continue + } + if lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { + continue + } + if lowestBlock == 0 || backendLatestBlockNumber < lowestBlock { lowestBlock = backendLatestBlockNumber lowestBlockHash = backendLatestBlockHash @@ -242,7 +294,20 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { consensusBackends = consensusBackends[:0] filteredBackendsNames = filteredBackendsNames[:0] for _, be := range cp.backendGroup.Backends { - if be.IsRateLimited() || !be.Online() || time.Now().Before(cp.backendState[be].bannedUntil) { + /* + a serving node needs to be: + - healthy (network) + - not rate limited + - online + - not banned + - with minimum peer count + - updated recently + */ + bs := cp.backendState[be] + notUpdated := bs.lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) + isBanned := time.Now().Before(bs.bannedUntil) + notEnoughPeers := bs.peerCount < cp.minPeerCount + if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers { filteredBackendsNames = append(filteredBackendsNames, be.Name) continue } @@ -291,6 +356,16 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { log.Info("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) } +// Unban remove any bans from the backends +func (cp *ConsensusPoller) Unban() { + for _, be := range cp.backendGroup.Backends { + bs := cp.backendState[be] + bs.backendStateMux.Lock() + bs.bannedUntil = time.Now().Add(-10 * time.Hour) + bs.backendStateMux.Unlock() + } +} + // fetchBlock Convenient wrapper to make a request to get a block directly from the backend func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block string) (blockNumber hexutil.Uint64, blockHash string, err error) { var rpcRes RPCRes @@ -301,7 +376,7 @@ func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block st jsonMap, ok := rpcRes.Result.(map[string]interface{}) if !ok { - return 0, "", fmt.Errorf("unexpected response type checking consensus on backend %s", be.Name) + return 0, "", fmt.Errorf("unexpected response to eth_getBlockByNumber on backend %s", be.Name) } blockNumber = hexutil.Uint64(hexutil.MustDecodeUint64(jsonMap["number"].(string))) blockHash = jsonMap["hash"].(string) @@ -309,19 +384,67 @@ func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block st return } -func (cp *ConsensusPoller) getBackendState(be *Backend) (blockNumber hexutil.Uint64, blockHash string) { +// isSyncing Convenient wrapper to check if the backend is syncing from the network +func (cp *ConsensusPoller) getPeerCount(ctx context.Context, be *Backend) (count uint64, err error) { + var rpcRes RPCRes + err = be.ForwardRPC(ctx, &rpcRes, "67", "net_peerCount") + if err != nil { + return 0, err + } + + jsonMap, ok := rpcRes.Result.(string) + if !ok { + return 0, fmt.Errorf("unexpected response to net_peerCount on backend %s", be.Name) + } + + count = hexutil.MustDecodeUint64(jsonMap) + + return count, nil +} + +// isInSync is a convenient wrapper to check if the backend is in sync from the network +func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bool, err error) { + var rpcRes RPCRes + err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_syncing") + if err != nil { + return false, err + } + + var res bool + switch typed := rpcRes.Result.(type) { + case bool: + syncing := typed + res = !syncing + case string: + syncing, err := strconv.ParseBool(typed) + if err != nil { + return false, err + } + res = !syncing + default: + // result is a json when not in sync + res = false + } + + return res, nil +} + +func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, blockNumber hexutil.Uint64, blockHash string, lastUpdate time.Time) { bs := cp.backendState[be] bs.backendStateMux.Lock() + peerCount = bs.peerCount blockNumber = bs.latestBlockNumber blockHash = bs.latestBlockHash + lastUpdate = bs.lastUpdate bs.backendStateMux.Unlock() return } -func (cp *ConsensusPoller) setBackendState(be *Backend, blockNumber hexutil.Uint64, blockHash string) (changed bool) { +func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, blockNumber hexutil.Uint64, blockHash string) (changed bool) { bs := cp.backendState[be] bs.backendStateMux.Lock() changed = bs.latestBlockHash != blockHash + bs.peerCount = peerCount bs.latestBlockNumber = blockNumber bs.latestBlockHash = blockHash bs.lastUpdate = time.Now() diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index da085bf..6875118 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -2,12 +2,14 @@ package integration_tests import ( "context" - "fmt" + "encoding/json" "net/http" "os" "path" "testing" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum-optimism/optimism/proxyd" ms "github.com/ethereum-optimism/optimism/proxyd/tools/mockserver/handler" "github.com/stretchr/testify/require" @@ -54,6 +56,7 @@ func TestConsensus(t *testing.T) { t.Run("initial consensus", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() + bg.Consensus.Unban() // unknown consensus at init require.Equal(t, "0x0", bg.Consensus.GetConsensusBlockNumber().String()) @@ -68,9 +71,64 @@ func TestConsensus(t *testing.T) { require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) }) + t.Run("prevent using a backend with low peer count", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + // advance latest on node2 to 0x2 + h1.AddOverride(&ms.MethodTemplate{ + Method: "net_peerCount", + Block: "", + Response: buildPeerCountResponse(1), + }) + + be := backend(bg, "node1") + require.NotNil(t, be) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + consensusGroup := bg.Consensus.GetConsensusGroup() + + require.NotContains(t, consensusGroup, be) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("prevent using a backend not in sync", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + // advance latest on node2 to 0x2 + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_syncing", + Block: "", + Response: buildResponse(map[string]string{ + "startingblock": "0x0", + "currentblock": "0x0", + "highestblock": "0x100", + }), + }) + + be := backend(bg, "node1") + require.NotNil(t, be) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + consensusGroup := bg.Consensus.GetConsensusGroup() + + require.NotContains(t, consensusGroup, be) + require.Equal(t, 1, len(consensusGroup)) + }) + t.Run("advance consensus", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() + bg.Consensus.Unban() for _, be := range bg.Backends { bg.Consensus.UpdateBackend(ctx, be) @@ -84,14 +142,13 @@ func TestConsensus(t *testing.T) { h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x2", "hash2"), + Response: buildGetBlockResponse("0x2", "hash2"), }) // poll for group consensus for _, be := range bg.Backends { bg.Consensus.UpdateBackend(ctx, be) } - bg.Consensus.UpdateBackendGroupConsensus(ctx) // consensus should stick to 0x1, since node1 is still lagging there bg.Consensus.UpdateBackendGroupConsensus(ctx) @@ -101,7 +158,7 @@ func TestConsensus(t *testing.T) { h1.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x2", "hash2"), + Response: buildGetBlockResponse("0x2", "hash2"), }) // poll for group consensus @@ -117,6 +174,7 @@ func TestConsensus(t *testing.T) { t.Run("broken consensus", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() + bg.Consensus.Unban() for _, be := range bg.Backends { bg.Consensus.UpdateBackend(ctx, be) @@ -130,12 +188,12 @@ func TestConsensus(t *testing.T) { h1.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x2", "hash2"), + Response: buildGetBlockResponse("0x2", "hash2"), }) h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x2", "hash2"), + Response: buildGetBlockResponse("0x2", "hash2"), }) // poll for group consensus @@ -151,7 +209,7 @@ func TestConsensus(t *testing.T) { h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "0x2", - Response: buildResponse("0x2", "wrong_hash"), + Response: buildGetBlockResponse("0x2", "wrong_hash"), }) // poll for group consensus @@ -169,6 +227,7 @@ func TestConsensus(t *testing.T) { t.Run("broken consensus with depth 2", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() + bg.Consensus.Unban() for _, be := range bg.Backends { bg.Consensus.UpdateBackend(ctx, be) @@ -182,12 +241,12 @@ func TestConsensus(t *testing.T) { h1.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x2", "hash2"), + Response: buildGetBlockResponse("0x2", "hash2"), }) h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x2", "hash2"), + Response: buildGetBlockResponse("0x2", "hash2"), }) // poll for group consensus @@ -203,12 +262,12 @@ func TestConsensus(t *testing.T) { h1.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x3", "hash3"), + Response: buildGetBlockResponse("0x3", "hash3"), }) h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x3", "hash3"), + Response: buildGetBlockResponse("0x3", "hash3"), }) // poll for group consensus @@ -224,12 +283,12 @@ func TestConsensus(t *testing.T) { h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "0x2", - Response: buildResponse("0x2", "wrong_hash2"), + Response: buildGetBlockResponse("0x2", "wrong_hash2"), }) h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "0x3", - Response: buildResponse("0x3", "wrong_hash3"), + Response: buildGetBlockResponse("0x3", "wrong_hash3"), }) // poll for group consensus @@ -245,6 +304,7 @@ func TestConsensus(t *testing.T) { t.Run("fork in advanced block", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() + bg.Consensus.Unban() for _, be := range bg.Backends { bg.Consensus.UpdateBackend(ctx, be) @@ -258,32 +318,32 @@ func TestConsensus(t *testing.T) { h1.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "0x2", - Response: buildResponse("0x2", "node1_0x2"), + Response: buildGetBlockResponse("0x2", "node1_0x2"), }) h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "0x2", - Response: buildResponse("0x2", "node2_0x2"), + Response: buildGetBlockResponse("0x2", "node2_0x2"), }) h1.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "0x3", - Response: buildResponse("0x3", "node1_0x3"), + Response: buildGetBlockResponse("0x3", "node1_0x3"), }) h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "0x3", - Response: buildResponse("0x3", "node2_0x3"), + Response: buildGetBlockResponse("0x3", "node2_0x3"), }) h1.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x3", "node1_0x3"), + Response: buildGetBlockResponse("0x3", "node1_0x3"), }) h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", - Response: buildResponse("0x3", "node2_0x3"), + Response: buildGetBlockResponse("0x3", "node2_0x3"), }) // poll for group consensus @@ -297,13 +357,31 @@ func TestConsensus(t *testing.T) { }) } -func buildResponse(number string, hash string) string { - return fmt.Sprintf(`{ - "jsonrpc": "2.0", - "id": 67, - "result": { - "number": "%s", - "hash": "%s" - } - }`, number, hash) +func backend(bg *proxyd.BackendGroup, name string) *proxyd.Backend { + for _, be := range bg.Backends { + if be.Name == name { + return be + } + } + return nil +} + +func buildPeerCountResponse(count uint64) string { + return buildResponse(hexutil.Uint64(count).String()) +} +func buildGetBlockResponse(number string, hash string) string { + return buildResponse(map[string]string{ + "number": number, + "hash": hash, + }) +} + +func buildResponse(result interface{}) string { + res, err := json.Marshal(proxyd.RPCRes{ + Result: result, + }) + if err != nil { + panic(err) + } + return string(res) } diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml index 9d4f2d1..10b8f52 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml +++ b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml @@ -1,3 +1,17 @@ +- method: net_peerCount + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": "0x10" + } +- method: eth_syncing + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": false + } - method: eth_getBlockByNumber block: latest response: > diff --git a/proxyd/proxyd/pkg/avg-sliding-window/sliding.go b/proxyd/proxyd/pkg/avg-sliding-window/sliding.go index 721daba..b0f2873 100644 --- a/proxyd/proxyd/pkg/avg-sliding-window/sliding.go +++ b/proxyd/proxyd/pkg/avg-sliding-window/sliding.go @@ -97,12 +97,17 @@ func (sw *AvgSlidingWindow) inWindow(t time.Time) bool { return windowStart.Before(t) && !t.After(now) } -// Add inserts a new data point into the window, with value `val` with the current time +// Add inserts a new data point into the window, with value `val` and the current time func (sw *AvgSlidingWindow) Add(val float64) { t := sw.clock.Now() sw.AddWithTime(t, val) } +// Incr is an alias to insert a data point with value float64(1) and the current time +func (sw *AvgSlidingWindow) Incr() { + sw.Add(1) +} + // AddWithTime inserts a new data point into the window, with value `val` and time `t` func (sw *AvgSlidingWindow) AddWithTime(t time.Time, val float64) { sw.advance() From f447404608bbc51369d8c34384765e4c5ef24e48 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 26 Apr 2023 13:07:01 -0700 Subject: [PATCH 053/212] sliding window thread safe --- proxyd/proxyd/pkg/avg-sliding-window/sliding.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/proxyd/proxyd/pkg/avg-sliding-window/sliding.go b/proxyd/proxyd/pkg/avg-sliding-window/sliding.go index b0f2873..70c40be 100644 --- a/proxyd/proxyd/pkg/avg-sliding-window/sliding.go +++ b/proxyd/proxyd/pkg/avg-sliding-window/sliding.go @@ -1,6 +1,7 @@ package avg_sliding_window import ( + "sync" "time" lm "github.com/emirpasic/gods/maps/linkedhashmap" @@ -44,6 +45,7 @@ type bucket struct { // Data points are rounded to nearest bucket of size `bucketSize`, // and evicted when they are too old based on `windowLength` type AvgSlidingWindow struct { + mux sync.Mutex bucketSize time.Duration windowLength time.Duration clock Clock @@ -112,6 +114,9 @@ func (sw *AvgSlidingWindow) Incr() { func (sw *AvgSlidingWindow) AddWithTime(t time.Time, val float64) { sw.advance() + defer sw.mux.Unlock() + sw.mux.Lock() + key := t.Round(sw.bucketSize) if !sw.inWindow(key) { return @@ -139,6 +144,8 @@ func (sw *AvgSlidingWindow) AddWithTime(t time.Time, val float64) { // advance evicts old data points func (sw *AvgSlidingWindow) advance() { + defer sw.mux.Unlock() + sw.mux.Lock() now := sw.clock.Now().Round(sw.bucketSize) windowStart := now.Add(-sw.windowLength) keys := sw.buckets.Keys() From b7f559c4c25d042110bcb579eddff0b02b48f315 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 27 Apr 2023 12:10:30 -0700 Subject: [PATCH 054/212] fix http code for unhealthy backends --- proxyd/proxyd/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 80e75fd..4fcfb26 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -88,7 +88,7 @@ var ( ErrNotHealthy = &RPCErr{ Code: JSONRPCErrorInternal - 18, Message: "backend is currently not healthy to serve traffic", - HTTPErrorCode: 429, + HTTPErrorCode: 503, } ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") From 58e3a35677392900e6e9c569b98feb846c7ca08b Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 27 Apr 2023 17:13:18 -0700 Subject: [PATCH 055/212] proxyd: round-robin lb for consensus group --- proxyd/proxyd/backend.go | 39 +++++++++++++++++- .../integration_tests/consensus_test.go | 41 +++++++++++++++++++ .../integration_tests/testdata/consensus.toml | 2 +- .../testdata/consensus_responses.yml | 7 ++++ 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 4fcfb26..ed95ac9 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -591,9 +591,46 @@ func (b *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch b return nil, nil } + backends := b.Backends + + // When `consensus_aware` is set to `true`, the backend group acts as a load balancer + // serving traffic from any backend that agrees in the consensus group + if b.Consensus != nil { + cg := b.Consensus.GetConsensusGroup() + backendsHealthy := make([]*Backend, 0, len(cg)) + backendsDegraded := make([]*Backend, 0, len(cg)) + // separate into unhealthy, degraded and healthy backends + for _, be := range cg { + // unhealthy are filtered out and not attempted + if !be.IsHealthy() { + continue + } + if be.IsDegraded() { + backendsDegraded = append(backendsDegraded, be) + continue + } + backendsHealthy = append(backendsHealthy, be) + } + + // shuffle both slices + r := rand.New(rand.NewSource(time.Now().UnixNano())) + r.Shuffle(len(backendsHealthy), func(i, j int) { + backendsHealthy[i], backendsHealthy[j] = backendsHealthy[j], backendsHealthy[i] + }) + r = rand.New(rand.NewSource(time.Now().UnixNano())) + r.Shuffle(len(backendsDegraded), func(i, j int) { + backendsDegraded[i], backendsDegraded[j] = backendsDegraded[j], backendsDegraded[i] + }) + + // healthy are put into a priority position + // degraded backends are used as fallback + backends = backendsHealthy + backends = append(backends, backendsDegraded...) + } + rpcRequestsTotal.Inc() - for _, back := range b.Backends { + for _, back := range backends { res, err := back.Forward(ctx, rpcReqs, isBatch) if errors.Is(err, ErrMethodNotWhitelisted) { return nil, err diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 6875118..b55a30c 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -3,6 +3,7 @@ package integration_tests import ( "context" "encoding/json" + "fmt" "net/http" "os" "path" @@ -47,6 +48,7 @@ func TestConsensus(t *testing.T) { ctx := context.Background() svr, shutdown, err := proxyd.Start(config) require.NoError(t, err) + client := NewProxydClient("http://127.0.0.1:8545") defer shutdown() bg := svr.BackendGroups["node"] @@ -355,6 +357,45 @@ func TestConsensus(t *testing.T) { // should resolve to 0x1, the highest common ancestor require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) }) + + t.Run("load balancing should hit both backends", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) + + node1.Reset() + node2.Reset() + + require.Equal(t, 0, len(node1.Requests())) + require.Equal(t, 0, len(node2.Requests())) + + // there is a random component to this test, + // since our round-robin implementation shuffles the ordering + // to achieve uniform distribution + + // so we just make 100 requests per backend and expect the number of requests to be somewhat balanced + // i.e. each backend should be hit minimally by at least 50% of the requests + consensusGroup := bg.Consensus.GetConsensusGroup() + + numberReqs := len(consensusGroup) * 100 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x1", false}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + numberReqs-- + } + + msg := fmt.Sprintf("n1 %d, n2 %d", len(node1.Requests()), len(node2.Requests())) + require.GreaterOrEqual(t, len(node1.Requests()), 50, msg) + require.GreaterOrEqual(t, len(node2.Requests()), 50, msg) + }) } func backend(bg *proxyd.BackendGroup, name string) *proxyd.Backend { diff --git a/proxyd/proxyd/integration_tests/testdata/consensus.toml b/proxyd/proxyd/integration_tests/testdata/consensus.toml index dbd8e26..bc9b43e 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus.toml +++ b/proxyd/proxyd/integration_tests/testdata/consensus.toml @@ -1,5 +1,5 @@ [server] -rpc_port = 8080 +rpc_port = 8545 [backend] response_timeout_seconds = 1 diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml index 10b8f52..83579e7 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml +++ b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml @@ -1,3 +1,10 @@ +- method: eth_chainId + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": "hello", + } - method: net_peerCount response: > { From c2ba3b1c559afc09aff226511b1562c715b56617 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 28 Apr 2023 11:51:05 -0700 Subject: [PATCH 056/212] address comments --- proxyd/proxyd/backend.go | 65 ++++++++++--------- .../integration_tests/consensus_test.go | 39 ++++++++++- 2 files changed, 73 insertions(+), 31 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index ed95ac9..8445af3 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -596,36 +596,7 @@ func (b *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch b // When `consensus_aware` is set to `true`, the backend group acts as a load balancer // serving traffic from any backend that agrees in the consensus group if b.Consensus != nil { - cg := b.Consensus.GetConsensusGroup() - backendsHealthy := make([]*Backend, 0, len(cg)) - backendsDegraded := make([]*Backend, 0, len(cg)) - // separate into unhealthy, degraded and healthy backends - for _, be := range cg { - // unhealthy are filtered out and not attempted - if !be.IsHealthy() { - continue - } - if be.IsDegraded() { - backendsDegraded = append(backendsDegraded, be) - continue - } - backendsHealthy = append(backendsHealthy, be) - } - - // shuffle both slices - r := rand.New(rand.NewSource(time.Now().UnixNano())) - r.Shuffle(len(backendsHealthy), func(i, j int) { - backendsHealthy[i], backendsHealthy[j] = backendsHealthy[j], backendsHealthy[i] - }) - r = rand.New(rand.NewSource(time.Now().UnixNano())) - r.Shuffle(len(backendsDegraded), func(i, j int) { - backendsDegraded[i], backendsDegraded[j] = backendsDegraded[j], backendsDegraded[i] - }) - - // healthy are put into a priority position - // degraded backends are used as fallback - backends = backendsHealthy - backends = append(backends, backendsDegraded...) + backends = b.loadBalancedConsensusGroup() } rpcRequestsTotal.Inc() @@ -707,6 +678,40 @@ func (b *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, return nil, ErrNoBackends } +func (b *BackendGroup) loadBalancedConsensusGroup() []*Backend { + cg := b.Consensus.GetConsensusGroup() + + backendsHealthy := make([]*Backend, 0, len(cg)) + backendsDegraded := make([]*Backend, 0, len(cg)) + // separate into healthy, degraded and unhealthy backends + for _, be := range cg { + // unhealthy are filtered out and not attempted + if !be.IsHealthy() { + continue + } + if be.IsDegraded() { + backendsDegraded = append(backendsDegraded, be) + continue + } + backendsHealthy = append(backendsHealthy, be) + } + + // shuffle both slices + r := rand.New(rand.NewSource(time.Now().UnixNano())) + r.Shuffle(len(backendsHealthy), func(i, j int) { + backendsHealthy[i], backendsHealthy[j] = backendsHealthy[j], backendsHealthy[i] + }) + r.Shuffle(len(backendsDegraded), func(i, j int) { + backendsDegraded[i], backendsDegraded[j] = backendsDegraded[j], backendsDegraded[i] + }) + + // healthy are put into a priority position + // degraded backends are used as fallback + backendsHealthy = append(backendsHealthy, backendsDegraded...) + + return backendsHealthy +} + func calcBackoff(i int) time.Duration { jitter := float64(rand.Int63n(250)) ms := math.Min(math.Pow(2, float64(i))*1000+jitter, 3000) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index b55a30c..aa7b7e1 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -78,7 +78,6 @@ func TestConsensus(t *testing.T) { h2.ResetOverrides() bg.Consensus.Unban() - // advance latest on node2 to 0x2 h1.AddOverride(&ms.MethodTemplate{ Method: "net_peerCount", Block: "", @@ -396,6 +395,44 @@ func TestConsensus(t *testing.T) { require.GreaterOrEqual(t, len(node1.Requests()), 50, msg) require.GreaterOrEqual(t, len(node2.Requests()), 50, msg) }) + + t.Run("load balancing should not hit if node is not healthy", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + // node1 should not be serving any traffic + h1.AddOverride(&ms.MethodTemplate{ + Method: "net_peerCount", + Block: "", + Response: buildPeerCountResponse(1), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + + node1.Reset() + node2.Reset() + + require.Equal(t, 0, len(node1.Requests())) + require.Equal(t, 0, len(node2.Requests())) + + numberReqs := 10 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x1", false}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + numberReqs-- + } + + msg := fmt.Sprintf("n1 %d, n2 %d", len(node1.Requests()), len(node2.Requests())) + require.Equal(t, len(node1.Requests()), 0, msg) + require.Equal(t, len(node2.Requests()), 10, msg) + }) } func backend(bg *proxyd.BackendGroup, name string) *proxyd.Backend { From d752c9bf9e157634c56be6f8e3edd83aadd173b9 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 3 May 2023 16:23:43 -0700 Subject: [PATCH 057/212] externalize configs --- proxyd/proxyd/backend.go | 6 +++++ proxyd/proxyd/config.go | 22 ++++++++++------ proxyd/proxyd/example.config.toml | 14 +++++++++++ .../integration_tests/testdata/consensus.toml | 3 +++ proxyd/proxyd/proxyd.go | 25 +++++++++++++++++-- 5 files changed, 61 insertions(+), 9 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 8445af3..f50926b 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -202,6 +202,12 @@ func WithProxydIP(ip string) BackendOpt { } } +func WithMaxDegradedLatencyThreshold(maxDegradedLatencyThreshold time.Duration) BackendOpt { + return func(b *Backend) { + b.maxDegradedLatencyThreshold = maxDegradedLatencyThreshold + } +} + func WithMaxLatencyThreshold(maxLatencyThreshold time.Duration) BackendOpt { return func(b *Backend) { b.maxLatencyThreshold = maxLatencyThreshold diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 94c1f83..0222a83 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -71,10 +71,13 @@ func (t *TOMLDuration) UnmarshalText(b []byte) error { } type BackendOptions struct { - ResponseTimeoutSeconds int `toml:"response_timeout_seconds"` - MaxResponseSizeBytes int64 `toml:"max_response_size_bytes"` - MaxRetries int `toml:"max_retries"` - OutOfServiceSeconds int `toml:"out_of_service_seconds"` + ResponseTimeoutSeconds int `toml:"response_timeout_seconds"` + MaxResponseSizeBytes int64 `toml:"max_response_size_bytes"` + MaxRetries int `toml:"max_retries"` + OutOfServiceSeconds int `toml:"out_of_service_seconds"` + MaxDegradedLatencyThreshold TOMLDuration `toml:"max_degraded_latency_threshold"` + MaxLatencyThreshold TOMLDuration `toml:"max_latency_threshold"` + MaxErrorRateThreshold float64 `toml:"max_error_rate_threshold"` } type BackendConfig struct { @@ -94,9 +97,14 @@ type BackendConfig struct { type BackendsConfig map[string]*BackendConfig type BackendGroupConfig struct { - Backends []string `toml:"backends"` - ConsensusAware bool `toml:"consensus_aware"` - ConsensusAsyncHandler string `toml:"consensus_handler"` + Backends []string `toml:"backends"` + + ConsensusAware bool `toml:"consensus_aware"` + ConsensusAsyncHandler string `toml:"consensus_handler"` + + ConsensusBanPeriod TOMLDuration `toml:"consensus_ban_period"` + ConsensusMaxUpdateThreshold TOMLDuration `toml:"consensus_max_update_threshold"` + ConsensusMinPeerCount int `toml:"consensus_min_peer_count"` } type BackendGroupsConfig map[string]*BackendGroupConfig diff --git a/proxyd/proxyd/example.config.toml b/proxyd/proxyd/example.config.toml index 16408e8..053b5fd 100644 --- a/proxyd/proxyd/example.config.toml +++ b/proxyd/proxyd/example.config.toml @@ -44,6 +44,12 @@ max_response_size_bytes = 5242880 max_retries = 3 # Number of seconds to wait before trying an unhealthy backend again. out_of_service_seconds = 600 +# Maximum latency accepted to serve requests, default 10s +max_latency_threshold = "30s" +# Maximum latency accepted to serve requests before degraded, default 5s +max_degraded_latency_threshold = "10s" +# Maximum error rate accepted to serve requests, default 0.5 (i.e. 50%) +max_error_rate_threshold = 0.3 [backends] # A map of backends by name. @@ -78,6 +84,14 @@ max_ws_conns = 1 [backend_groups] [backend_groups.main] backends = ["infura"] +# Enable consensus awareness for backend group, making it act as a load balancer, default false +# consensus_aware = true +# Period in which the backend wont serve requests if banned, default 5m +# consensus_ban_period = "1m" +# Maximum delay for update the backend, default 30s +# consensus_max_update_threshold = "20s" +# Minimum peer count, default 3 +# consensus_min_peer_count = 4 [backend_groups.alchemy] backends = ["alchemy"] diff --git a/proxyd/proxyd/integration_tests/testdata/consensus.toml b/proxyd/proxyd/integration_tests/testdata/consensus.toml index bc9b43e..d26b9dc 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus.toml +++ b/proxyd/proxyd/integration_tests/testdata/consensus.toml @@ -16,6 +16,9 @@ rpc_url = "$NODE2_URL" backends = ["node1", "node2"] consensus_aware = true consensus_handler = "noop" # allow more control over the consensus poller for tests +consensus_ban_period = "1m" +consensus_max_update_threshold = "2m" +consensus_min_peer_count = 4 [rpc_method_mappings] eth_call = "node" diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 5a34fa6..f682272 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -123,6 +123,15 @@ func Start(config *Config) (*Server, func(), error) { if config.BackendOptions.OutOfServiceSeconds != 0 { opts = append(opts, WithOutOfServiceDuration(secondsToDuration(config.BackendOptions.OutOfServiceSeconds))) } + if config.BackendOptions.MaxDegradedLatencyThreshold > 0 { + opts = append(opts, WithMaxDegradedLatencyThreshold(time.Duration(config.BackendOptions.MaxDegradedLatencyThreshold))) + } + if config.BackendOptions.MaxLatencyThreshold > 0 { + opts = append(opts, WithMaxLatencyThreshold(time.Duration(config.BackendOptions.MaxLatencyThreshold))) + } + if config.BackendOptions.MaxErrorRateThreshold > 0 { + opts = append(opts, WithMaxErrorRateThreshold(config.BackendOptions.MaxErrorRateThreshold)) + } if cfg.MaxRPS != 0 { opts = append(opts, WithMaxRPS(cfg.MaxRPS)) } @@ -148,6 +157,7 @@ func Start(config *Config) (*Server, func(), error) { opts = append(opts, WithStrippedTrailingXFF()) } opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP"))) + back := NewBackend(name, rpcURL, wsURL, lim, rpcRequestSemaphore, opts...) backendNames = append(backendNames, name) backendsByName[name] = back @@ -302,14 +312,25 @@ func Start(config *Config) (*Server, func(), error) { } for bgName, bg := range backendGroups { - if config.BackendGroups[bgName].ConsensusAware { + bgcfg := config.BackendGroups[bgName] + if bgcfg.ConsensusAware { log.Info("creating poller for consensus aware backend_group", "name", bgName) copts := make([]ConsensusOpt, 0) - if config.BackendGroups[bgName].ConsensusAsyncHandler == "noop" { + if bgcfg.ConsensusAsyncHandler == "noop" { copts = append(copts, WithAsyncHandler(NewNoopAsyncHandler())) } + if bgcfg.ConsensusBanPeriod > 0 { + copts = append(copts, WithBanPeriod(time.Duration(bgcfg.ConsensusBanPeriod))) + } + if bgcfg.ConsensusMaxUpdateThreshold > 0 { + copts = append(copts, WithMaxUpdateThreshold(time.Duration(bgcfg.ConsensusMaxUpdateThreshold))) + } + if bgcfg.ConsensusMinPeerCount > 0 { + copts = append(copts, WithMinPeerCount(uint64(bgcfg.ConsensusMinPeerCount))) + } + cp := NewConsensusPoller(bg, copts...) bg.Consensus = cp } From 7c9dd519e0d5776545529f8ef65cbfecaf1f484d Mon Sep 17 00:00:00 2001 From: felipe-op <130432649+felipe-op@users.noreply.github.com> Date: Thu, 4 May 2023 08:21:48 -0700 Subject: [PATCH 058/212] proxyd: re-write block tags to enforce consensus (#5586) --- proxyd/proxyd/backend.go | 124 +++-- .../integration_tests/consensus_test.go | 178 +++++++ proxyd/proxyd/rewriter.go | 175 +++++++ proxyd/proxyd/rewriter_test.go | 441 ++++++++++++++++++ .../tools/mockserver/handler/handler.go | 63 ++- 5 files changed, 930 insertions(+), 51 deletions(-) create mode 100644 proxyd/proxyd/rewriter.go create mode 100644 proxyd/proxyd/rewriter_test.go diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 8445af3..eb23d9c 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -90,6 +90,11 @@ var ( Message: "backend is currently not healthy to serve traffic", HTTPErrorCode: 503, } + ErrBlockOutOfRange = &RPCErr{ + Code: JSONRPCErrorInternal - 19, + Message: "block is out of range", + HTTPErrorCode: 400, + } ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") ) @@ -214,6 +219,12 @@ func WithMaxErrorRateThreshold(maxErrorRateThreshold float64) BackendOpt { } } +type indexedReqRes struct { + index int + req *RPCReq + res *RPCRes +} + func NewBackend( name string, rpcURL string, @@ -593,47 +604,96 @@ func (b *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch b backends := b.Backends - // When `consensus_aware` is set to `true`, the backend group acts as a load balancer - // serving traffic from any backend that agrees in the consensus group + overriddenResponses := make([]*indexedReqRes, 0) + rewrittenReqs := make([]*RPCReq, 0, len(rpcReqs)) + if b.Consensus != nil { + // When `consensus_aware` is set to `true`, the backend group acts as a load balancer + // serving traffic from any backend that agrees in the consensus group backends = b.loadBalancedConsensusGroup() + + // We also rewrite block tags to enforce compliance with consensus + rctx := RewriteContext{latest: b.Consensus.GetConsensusBlockNumber()} + + for i, req := range rpcReqs { + res := RPCRes{JSONRPC: JSONRPCVersion, ID: req.ID} + result, err := RewriteTags(rctx, req, &res) + switch result { + case RewriteOverrideError: + overriddenResponses = append(overriddenResponses, &indexedReqRes{ + index: i, + req: req, + res: &res, + }) + if errors.Is(err, ErrRewriteBlockOutOfRange) { + res.Error = ErrBlockOutOfRange + } else { + res.Error = ErrParseErr + } + case RewriteOverrideResponse: + overriddenResponses = append(overriddenResponses, &indexedReqRes{ + index: i, + req: req, + res: &res, + }) + case RewriteOverrideRequest, RewriteNone: + rewrittenReqs = append(rewrittenReqs, req) + } + } + rpcReqs = rewrittenReqs } rpcRequestsTotal.Inc() for _, back := range backends { - res, err := back.Forward(ctx, rpcReqs, isBatch) - if errors.Is(err, ErrMethodNotWhitelisted) { - return nil, err + res := make([]*RPCRes, 0) + var err error + + if len(rpcReqs) > 0 { + res, err = back.Forward(ctx, rpcReqs, isBatch) + if errors.Is(err, ErrMethodNotWhitelisted) { + return nil, err + } + if errors.Is(err, ErrBackendOffline) { + log.Warn( + "skipping offline backend", + "name", back.Name, + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + continue + } + if errors.Is(err, ErrBackendOverCapacity) { + log.Warn( + "skipping over-capacity backend", + "name", back.Name, + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + continue + } + if err != nil { + log.Error( + "error forwarding request to backend", + "name", back.Name, + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + "err", err, + ) + continue + } } - if errors.Is(err, ErrBackendOffline) { - log.Warn( - "skipping offline backend", - "name", back.Name, - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - continue - } - if errors.Is(err, ErrBackendOverCapacity) { - log.Warn( - "skipping over-capacity backend", - "name", back.Name, - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - continue - } - if err != nil { - log.Error( - "error forwarding request to backend", - "name", back.Name, - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - "err", err, - ) - continue + + // re-apply overridden responses + for _, ov := range overriddenResponses { + if len(res) > 0 { + // insert ov.res at position ov.index + res = append(res[:ov.index], append([]*RPCRes{ov.res}, res[ov.index:]...)...) + } else { + res = append(res, ov.res) + } } + return res, nil } diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index aa7b7e1..51d1f27 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -433,6 +433,184 @@ func TestConsensus(t *testing.T) { require.Equal(t, len(node1.Requests()), 0, msg) require.Equal(t, len(node2.Requests()), 10, msg) }) + + t.Run("rewrite response of eth_blockNumber", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + node1.Reset() + node2.Reset() + bg.Consensus.Unban() + + // establish the consensus + + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x2", "hash2"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x2", "hash2"), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + totalRequests := len(node1.Requests()) + len(node2.Requests()) + + require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) + + // pretend backends advanced in consensus, but we are still serving the latest value of the consensus + // until it gets updated again + + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x3", "hash3"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x3", "hash3"), + }) + + resRaw, statusCode, err := client.SendRPC("eth_blockNumber", nil) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &jsonMap) + require.NoError(t, err) + require.Equal(t, "0x2", jsonMap["result"]) + + // no extra request hit the backends + require.Equal(t, totalRequests, len(node1.Requests())+len(node2.Requests())) + }) + + t.Run("rewrite request of eth_getBlockByNumber", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + // establish the consensus and ban node2 for now + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x2", "hash2"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "net_peerCount", + Block: "", + Response: buildPeerCountResponse(1), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + + node1.Reset() + + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"latest"}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(node1.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "0x2", jsonMap["params"].([]interface{})[0]) + }) + + t.Run("rewrite request of eth_getBlockByNumber - out of range", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + // establish the consensus and ban node2 for now + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x2", "hash2"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "net_peerCount", + Block: "", + Response: buildPeerCountResponse(1), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + + node1.Reset() + + resRaw, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x10"}) + require.NoError(t, err) + require.Equal(t, 400, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &jsonMap) + require.NoError(t, err) + require.Equal(t, -32019, int(jsonMap["error"].(map[string]interface{})["code"].(float64))) + require.Equal(t, "block is out of range", jsonMap["error"].(map[string]interface{})["message"]) + }) + + t.Run("batched rewrite", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + // establish the consensus and ban node2 for now + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x2", "hash2"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "net_peerCount", + Block: "", + Response: buildPeerCountResponse(1), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + + node1.Reset() + + resRaw, statusCode, err := client.SendBatchRPC( + NewRPCReq("1", "eth_getBlockByNumber", []interface{}{"latest"}), + NewRPCReq("2", "eth_getBlockByNumber", []interface{}{"0x10"}), + NewRPCReq("3", "eth_getBlockByNumber", []interface{}{"0x1"})) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap []map[string]interface{} + err = json.Unmarshal(resRaw, &jsonMap) + require.NoError(t, err) + require.Equal(t, 3, len(jsonMap)) + + // rewrite latest to 0x2 + require.Equal(t, "0x2", jsonMap[0]["result"].(map[string]interface{})["number"]) + + // out of bounds for block 0x10 + require.Equal(t, -32019, int(jsonMap[1]["error"].(map[string]interface{})["code"].(float64))) + require.Equal(t, "block is out of range", jsonMap[1]["error"].(map[string]interface{})["message"]) + + // dont rewrite for 0x1 + require.Equal(t, "0x1", jsonMap[2]["result"].(map[string]interface{})["number"]) + }) } func backend(bg *proxyd.BackendGroup, name string) *proxyd.Backend { diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go new file mode 100644 index 0000000..6b01c1a --- /dev/null +++ b/proxyd/proxyd/rewriter.go @@ -0,0 +1,175 @@ +package proxyd + +import ( + "encoding/json" + "errors" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +type RewriteContext struct { + latest hexutil.Uint64 +} + +type RewriteResult uint8 + +const ( + // RewriteNone means request should be forwarded as-is + RewriteNone RewriteResult = iota + + // RewriteOverrideError means there was an error attempting to rewrite + RewriteOverrideError + + // RewriteOverrideRequest means the modified request should be forwarded to the backend + RewriteOverrideRequest + + // RewriteOverrideResponse means to skip calling the backend and serve the overridden response + RewriteOverrideResponse +) + +var ( + ErrRewriteBlockOutOfRange = errors.New("block is out of range") +) + +// RewriteTags modifies the request and the response based on block tags +func RewriteTags(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { + rw, err := RewriteResponse(rctx, req, res) + if rw == RewriteOverrideResponse { + return rw, err + } + return RewriteRequest(rctx, req, res) +} + +// RewriteResponse modifies the response object to comply with the rewrite context +// after the method has been called at the backend +// RewriteResult informs the decision of the rewrite +func RewriteResponse(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { + switch req.Method { + case "eth_blockNumber": + res.Result = rctx.latest + return RewriteOverrideResponse, nil + } + return RewriteNone, nil +} + +// RewriteRequest modifies the request object to comply with the rewrite context +// before the method has been called at the backend +// it returns false if nothing was changed +func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { + switch req.Method { + case "eth_getLogs", + "eth_newFilter": + return rewriteRange(rctx, req, res, 0) + case "eth_getBalance", + "eth_getCode", + "eth_getTransactionCount", + "eth_call": + return rewriteParam(rctx, req, res, 1) + case "eth_getStorageAt": + return rewriteParam(rctx, req, res, 2) + case "eth_getBlockTransactionCountByNumber", + "eth_getUncleCountByBlockNumber", + "eth_getBlockByNumber", + "eth_getTransactionByBlockNumberAndIndex", + "eth_getUncleByBlockNumberAndIndex": + return rewriteParam(rctx, req, res, 0) + } + return RewriteNone, nil +} + +func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (RewriteResult, error) { + var p []interface{} + err := json.Unmarshal(req.Params, &p) + if err != nil { + return RewriteOverrideError, err + } + + if len(p) <= pos { + p = append(p, "latest") + } + + val, rw, err := rewriteTag(rctx, p[pos].(string)) + if err != nil { + return RewriteOverrideError, err + } + + if rw { + p[pos] = val + paramRaw, err := json.Marshal(p) + if err != nil { + return RewriteOverrideError, err + } + req.Params = paramRaw + return RewriteOverrideRequest, nil + } + return RewriteNone, nil +} + +func rewriteRange(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (RewriteResult, error) { + var p []map[string]interface{} + err := json.Unmarshal(req.Params, &p) + if err != nil { + return RewriteOverrideError, err + } + + modifiedFrom, err := rewriteTagMap(rctx, p[pos], "fromBlock") + if err != nil { + return RewriteOverrideError, err + } + + modifiedTo, err := rewriteTagMap(rctx, p[pos], "toBlock") + if err != nil { + return RewriteOverrideError, err + } + + // if any of the fields the request have been changed, re-marshal the params + if modifiedFrom || modifiedTo { + paramsRaw, err := json.Marshal(p) + req.Params = paramsRaw + if err != nil { + return RewriteOverrideError, err + } + return RewriteOverrideRequest, nil + } + + return RewriteNone, nil +} + +func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (bool, error) { + if m[key] == nil || m[key] == "" { + return false, nil + } + + current, ok := m[key].(string) + if !ok { + return false, errors.New("expected string") + } + + val, rw, err := rewriteTag(rctx, current) + if err != nil { + return false, err + } + if rw { + m[key] = val + return true, nil + } + + return false, nil +} + +func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { + if current == "latest" { + return rctx.latest.String(), true, nil + } else if strings.HasPrefix(current, "0x") { + decode, err := hexutil.DecodeUint64(current) + if err != nil { + return current, false, err + } + b := hexutil.Uint64(decode) + if b > rctx.latest { + return "", false, ErrRewriteBlockOutOfRange + } + } + return current, false, nil +} diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go new file mode 100644 index 0000000..566ccdb --- /dev/null +++ b/proxyd/proxyd/rewriter_test.go @@ -0,0 +1,441 @@ +package proxyd + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" +) + +type args struct { + rctx RewriteContext + req *RPCReq + res *RPCRes +} + +type rewriteTest struct { + name string + args args + expected RewriteResult + expectedErr error + check func(*testing.T, args) +} + +func TestRewriteRequest(t *testing.T) { + tests := []rewriteTest{ + /* range scoped */ + { + name: "eth_getLogs fromBlock latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["fromBlock"]) + }, + }, + { + name: "eth_getLogs fromBlock within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(55).String()}})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(55).String(), p[0]["fromBlock"]) + }, + }, + { + name: "eth_getLogs fromBlock out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(111).String()}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + { + name: "eth_getLogs toBlock latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["toBlock"]) + }, + }, + { + name: "eth_getLogs toBlock within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": hexutil.Uint64(55).String()}})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(55).String(), p[0]["toBlock"]) + }, + }, + { + name: "eth_getLogs toBlock out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": hexutil.Uint64(111).String()}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + { + name: "eth_getLogs fromBlock, toBlock latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "latest", "toBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["fromBlock"]) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["toBlock"]) + }, + }, + { + name: "eth_getLogs fromBlock, toBlock within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(55).String(), "toBlock": hexutil.Uint64(77).String()}})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(55).String(), p[0]["fromBlock"]) + require.Equal(t, hexutil.Uint64(77).String(), p[0]["toBlock"]) + }, + }, + { + name: "eth_getLogs fromBlock, toBlock out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(111).String(), "toBlock": hexutil.Uint64(222).String()}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + /* default block parameter */ + { + name: "eth_getCode omit block, should add", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 2, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, hexutil.Uint64(100).String(), p[1]) + }, + }, + { + name: "eth_getCode latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123", "latest"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 2, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, hexutil.Uint64(100).String(), p[1]) + }, + }, + { + name: "eth_getCode within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123", hexutil.Uint64(55).String()})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 2, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, hexutil.Uint64(55).String(), p[1]) + }, + }, + { + name: "eth_getCode out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123", hexutil.Uint64(111).String()})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + /* default block parameter, at position 2 */ + { + name: "eth_getStorageAt omit block, should add", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, "5", p[1]) + require.Equal(t, hexutil.Uint64(100).String(), p[2]) + }, + }, + { + name: "eth_getStorageAt latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5", "latest"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, "5", p[1]) + require.Equal(t, hexutil.Uint64(100).String(), p[2]) + }, + }, + { + name: "eth_getStorageAt within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5", hexutil.Uint64(55).String()})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, "5", p[1]) + require.Equal(t, hexutil.Uint64(55).String(), p[2]) + }, + }, + { + name: "eth_getStorageAt out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5", hexutil.Uint64(111).String()})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + /* default block parameter, at position 0 */ + { + name: "eth_getBlockByNumber omit block, should add", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(100).String(), p[0]) + }, + }, + { + name: "eth_getBlockByNumber latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"latest"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(100).String(), p[0]) + }, + }, + { + name: "eth_getBlockByNumber within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{hexutil.Uint64(55).String()})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(55).String(), p[0]) + }, + }, + { + name: "eth_getBlockByNumber out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{hexutil.Uint64(111).String()})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + } + + // generalize tests for other methods with same interface and behavior + tests = generalize(tests, "eth_getLogs", "eth_newFilter") + tests = generalize(tests, "eth_getCode", "eth_getBalance") + tests = generalize(tests, "eth_getCode", "eth_getTransactionCount") + tests = generalize(tests, "eth_getCode", "eth_call") + tests = generalize(tests, "eth_getBlockByNumber", "eth_getBlockTransactionCountByNumber") + tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleCountByBlockNumber") + tests = generalize(tests, "eth_getBlockByNumber", "eth_getTransactionByBlockNumberAndIndex") + tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleByBlockNumberAndIndex") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := RewriteRequest(tt.args.rctx, tt.args.req, tt.args.res) + if result != RewriteOverrideError { + require.Nil(t, err) + require.Equal(t, tt.expected, result) + } else { + require.Equal(t, tt.expectedErr, err) + } + if tt.check != nil { + tt.check(t, tt.args) + } + }) + } +} + +func generalize(tests []rewriteTest, baseMethod string, generalizedMethod string) []rewriteTest { + newCases := make([]rewriteTest, 0) + for _, t := range tests { + if t.args.req.Method == baseMethod { + newName := strings.Replace(t.name, baseMethod, generalizedMethod, -1) + var req *RPCReq + var res *RPCRes + + if t.args.req != nil { + req = &RPCReq{ + JSONRPC: t.args.req.JSONRPC, + Method: generalizedMethod, + Params: t.args.req.Params, + ID: t.args.req.ID, + } + } + + if t.args.res != nil { + res = &RPCRes{ + JSONRPC: t.args.res.JSONRPC, + Result: t.args.res.Result, + Error: t.args.res.Error, + ID: t.args.res.ID, + } + } + newCases = append(newCases, rewriteTest{ + name: newName, + args: args{ + rctx: t.args.rctx, + req: req, + res: res, + }, + expected: t.expected, + expectedErr: t.expectedErr, + check: t.check, + }) + } + } + return append(tests, newCases...) +} + +func TestRewriteResponse(t *testing.T) { + type args struct { + rctx RewriteContext + req *RPCReq + res *RPCRes + } + tests := []struct { + name string + args args + expected RewriteResult + check func(*testing.T, args) + }{ + { + name: "eth_blockNumber latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_blockNumber"}, + res: &RPCRes{Result: hexutil.Uint64(200)}, + }, + expected: RewriteOverrideResponse, + check: func(t *testing.T, args args) { + require.Equal(t, args.res.Result, hexutil.Uint64(100)) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := RewriteResponse(tt.args.rctx, tt.args.req, tt.args.res) + require.Nil(t, err) + require.Equal(t, tt.expected, result) + if tt.check != nil { + tt.check(t, tt.args) + } + }) + } +} diff --git a/proxyd/proxyd/tools/mockserver/handler/handler.go b/proxyd/proxyd/tools/mockserver/handler/handler.go index 18d6026..04f30e7 100644 --- a/proxyd/proxyd/tools/mockserver/handler/handler.go +++ b/proxyd/proxyd/tools/mockserver/handler/handler.go @@ -6,6 +6,9 @@ import ( "io" "net/http" "os" + "strings" + + "github.com/ethereum-optimism/optimism/proxyd" "github.com/gorilla/mux" "github.com/pkg/errors" @@ -46,12 +49,6 @@ func (mh *MockedHandler) Handler(w http.ResponseWriter, req *http.Request) { fmt.Printf("error reading request: %v\n", err) } - var j map[string]interface{} - err = json.Unmarshal(body, &j) - if err != nil { - fmt.Printf("error reading request: %v\n", err) - } - var template []*MethodTemplate if mh.Autoload { template = append(template, mh.LoadFromFile(mh.AutoloadFile)...) @@ -60,23 +57,51 @@ func (mh *MockedHandler) Handler(w http.ResponseWriter, req *http.Request) { template = append(template, mh.Overrides...) } - method := j["method"] - block := "" - if method == "eth_getBlockByNumber" { - block = (j["params"].([]interface{})[0]).(string) + batched := proxyd.IsBatch(body) + var requests []map[string]interface{} + if batched { + err = json.Unmarshal(body, &requests) + if err != nil { + fmt.Printf("error reading request: %v\n", err) + } + } else { + var j map[string]interface{} + err = json.Unmarshal(body, &j) + if err != nil { + fmt.Printf("error reading request: %v\n", err) + } + requests = append(requests, j) } - var selectedResponse *string - for _, r := range template { - if r.Method == method && r.Block == block { - selectedResponse = &r.Response + var responses []string + for _, r := range requests { + method := r["method"] + block := "" + if method == "eth_getBlockByNumber" { + block = (r["params"].([]interface{})[0]).(string) + } + + var selectedResponse string + for _, r := range template { + if r.Method == method && r.Block == block { + selectedResponse = r.Response + } + } + if selectedResponse != "" { + responses = append(responses, selectedResponse) } } - if selectedResponse != nil { - _, err := fmt.Fprintf(w, *selectedResponse) - if err != nil { - fmt.Printf("error writing response: %v\n", err) - } + + resBody := "" + if batched { + resBody = "[" + strings.Join(responses, ",") + "]" + } else { + resBody = responses[0] + } + + _, err = fmt.Fprint(w, resBody) + if err != nil { + fmt.Printf("error writing response: %v\n", err) } } From bb4a592d77f09234401e044e301efe43a12cfdf3 Mon Sep 17 00:00:00 2001 From: felipe-op <130432649+felipe-op@users.noreply.github.com> Date: Thu, 4 May 2023 09:53:11 -0700 Subject: [PATCH 059/212] proxyd/fix: error rate tolerance (#5606) --- proxyd/proxyd/backend.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 6b00a2e..9aec5d5 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -557,7 +557,11 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // IsHealthy checks if the backend is able to serve traffic, based on dynamic parameters func (b *Backend) IsHealthy() bool { - errorRate := b.networkErrorsSlidingWindow.Sum() / b.networkRequestsSlidingWindow.Sum() + errorRate := float64(0) + // avoid division-by-zero when the window is empty + if b.networkRequestsSlidingWindow.Sum() >= 10 { + errorRate = b.networkErrorsSlidingWindow.Sum() / b.networkRequestsSlidingWindow.Sum() + } avgLatency := time.Duration(b.latencySlidingWindow.Avg()) if errorRate >= b.maxErrorRateThreshold { return false From 538492a0e7bd7f3ce4e6fa4e7164fa79d89483d3 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 4 May 2023 15:33:08 -0700 Subject: [PATCH 060/212] rewrite should support blockorhash --- proxyd/proxyd/rewriter.go | 25 +++++++++++++++---------- proxyd/proxyd/rewriter_test.go | 12 ++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index 6b01c1a..0545365 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -3,9 +3,8 @@ package proxyd import ( "encoding/json" "errors" - "strings" - "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" ) type RewriteContext struct { @@ -159,15 +158,21 @@ func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (b } func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { - if current == "latest" { + jv, err := json.Marshal(current) + if err != nil { + return "", false, err + } + + var bnh rpc.BlockNumberOrHash + err = bnh.UnmarshalJSON(jv) + if err != nil { + return "", false, err + } + + if bnh.BlockNumber != nil && *bnh.BlockNumber == rpc.LatestBlockNumber { return rctx.latest.String(), true, nil - } else if strings.HasPrefix(current, "0x") { - decode, err := hexutil.DecodeUint64(current) - if err != nil { - return current, false, err - } - b := hexutil.Uint64(decode) - if b > rctx.latest { + } else if bnh.BlockNumber != nil { + if hexutil.Uint64(bnh.BlockNumber.Int64()) > rctx.latest { return "", false, ErrRewriteBlockOutOfRange } } diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go index 566ccdb..ed5e6a7 100644 --- a/proxyd/proxyd/rewriter_test.go +++ b/proxyd/proxyd/rewriter_test.go @@ -334,6 +334,18 @@ func TestRewriteRequest(t *testing.T) { expected: RewriteOverrideError, expectedErr: ErrRewriteBlockOutOfRange, }, + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08", + "0x1c4840bcb3de3ac403c0075b46c2c47d4396c5b624b6e1b2874ec04e8879b483"})}, + res: nil, + }, + expected: RewriteNone, + }, } // generalize tests for other methods with same interface and behavior From 1d3970995edb9354468062f449a19451a60b0bea Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 4 May 2023 15:36:37 -0700 Subject: [PATCH 061/212] goimports --- proxyd/proxyd/rewriter.go | 1 + 1 file changed, 1 insertion(+) diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index 0545365..35d78df 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -3,6 +3,7 @@ package proxyd import ( "encoding/json" "errors" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" ) From 75dcfd9a7a6973d170ee0bb37e58881abd1bd763 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 4 May 2023 15:46:08 -0700 Subject: [PATCH 062/212] proxyd/config: skip peer count check --- proxyd/proxyd/backend.go | 8 ++++++++ proxyd/proxyd/config.go | 23 ++++++++++++----------- proxyd/proxyd/consensus_poller.go | 17 ++++++++++------- proxyd/proxyd/example.config.toml | 3 +++ proxyd/proxyd/proxyd.go | 1 + 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 9aec5d5..9244315 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -132,6 +132,8 @@ type Backend struct { stripTrailingXFF bool proxydIP string + skipPeerCountCheck bool + maxDegradedLatencyThreshold time.Duration maxLatencyThreshold time.Duration maxErrorRateThreshold float64 @@ -207,6 +209,12 @@ func WithProxydIP(ip string) BackendOpt { } } +func WithSkipPeerCountCheck(skipPeerCountCheck bool) BackendOpt { + return func(b *Backend) { + b.skipPeerCountCheck = skipPeerCountCheck + } +} + func WithMaxDegradedLatencyThreshold(maxDegradedLatencyThreshold time.Duration) BackendOpt { return func(b *Backend) { b.maxDegradedLatencyThreshold = maxDegradedLatencyThreshold diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 0222a83..218dbc4 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -81,17 +81,18 @@ type BackendOptions struct { } type BackendConfig struct { - Username string `toml:"username"` - Password string `toml:"password"` - RPCURL string `toml:"rpc_url"` - WSURL string `toml:"ws_url"` - WSPort int `toml:"ws_port"` - MaxRPS int `toml:"max_rps"` - MaxWSConns int `toml:"max_ws_conns"` - CAFile string `toml:"ca_file"` - ClientCertFile string `toml:"client_cert_file"` - ClientKeyFile string `toml:"client_key_file"` - StripTrailingXFF bool `toml:"strip_trailing_xff"` + Username string `toml:"username"` + Password string `toml:"password"` + RPCURL string `toml:"rpc_url"` + WSURL string `toml:"ws_url"` + WSPort int `toml:"ws_port"` + MaxRPS int `toml:"max_rps"` + MaxWSConns int `toml:"max_ws_conns"` + CAFile string `toml:"ca_file"` + ClientCertFile string `toml:"client_cert_file"` + ClientKeyFile string `toml:"client_key_file"` + StripTrailingXFF bool `toml:"strip_trailing_xff"` + SkipPeerCountCheck bool `toml:"consensus_skip_peer_count"` } type BackendsConfig map[string]*BackendConfig diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 7d01c9b..308c516 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -227,10 +227,13 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - peerCount, err := cp.getPeerCount(ctx, be) - if err != nil { - log.Warn("error updating backend", "name", be.Name, "err", err) - return + var peerCount uint64 + if !be.skipPeerCountCheck { + peerCount, err = cp.getPeerCount(ctx, be) + if err != nil { + log.Warn("error updating backend", "name", be.Name, "err", err) + return + } } latestBlockNumber, latestBlockHash, err := cp.fetchBlock(ctx, be, "latest") @@ -257,7 +260,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { for _, be := range cp.backendGroup.Backends { peerCount, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate := cp.getBackendState(be) - if peerCount < cp.minPeerCount { + if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { continue } if lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { @@ -306,7 +309,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { bs := cp.backendState[be] notUpdated := bs.lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) isBanned := time.Now().Before(bs.bannedUntil) - notEnoughPeers := bs.peerCount < cp.minPeerCount + notEnoughPeers := !be.skipPeerCountCheck && bs.peerCount < cp.minPeerCount if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers { filteredBackendsNames = append(filteredBackendsNames, be.Name) continue @@ -384,7 +387,7 @@ func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block st return } -// isSyncing Convenient wrapper to check if the backend is syncing from the network +// getPeerCount Convenient wrapper to retrieve the current peer count from the backend func (cp *ConsensusPoller) getPeerCount(ctx context.Context, be *Backend) (count uint64, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "net_peerCount") diff --git a/proxyd/proxyd/example.config.toml b/proxyd/proxyd/example.config.toml index 053b5fd..cb41614 100644 --- a/proxyd/proxyd/example.config.toml +++ b/proxyd/proxyd/example.config.toml @@ -72,6 +72,9 @@ ca_file = "" client_cert_file = "" # Path to a custom client key file. client_key_file = "" +# Allows backends to skip peer count checking, default false +# consensus_skip_peer_count = true + [backends.alchemy] rpc_url = "" diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index f682272..cd02074 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -157,6 +157,7 @@ func Start(config *Config) (*Server, func(), error) { opts = append(opts, WithStrippedTrailingXFF()) } opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP"))) + opts = append(opts, WithSkipPeerCountCheck(cfg.SkipPeerCountCheck)) back := NewBackend(name, rpcURL, wsURL, lim, rpcRequestSemaphore, opts...) backendNames = append(backendNames, name) From 5f4ead67a7aca7c7627d50a2ce422fbd96facf4a Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 4 May 2023 16:39:22 -0700 Subject: [PATCH 063/212] adjust log level --- proxyd/proxyd/consensus_poller.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 7d01c9b..6311993 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -205,7 +205,7 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { bs := cp.backendState[be] if time.Now().Before(bs.bannedUntil) { - log.Warn("skipping backend banned", "backend", be.Name, "bannedUntil", bs.bannedUntil) + log.Debug("skipping backend banned", "backend", be.Name, "bannedUntil", bs.bannedUntil) return } @@ -243,7 +243,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { if changed { RecordBackendLatestBlock(be, latestBlockNumber) - log.Info("backend state updated", "name", be.Name, "state", bs) + log.Debug("backend state updated", "name", be.Name, "state", bs) } } @@ -285,7 +285,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { filteredBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) if lowestBlock > currentConsensusBlockNumber { - log.Info("validating consensus on block", lowestBlock) + log.Debug("validating consensus on block", "lowestBlock", lowestBlock) } broken := false @@ -338,7 +338,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { // walk one block behind and try again proposedBlock -= 1 proposedBlockHash = "" - log.Info("no consensus, now trying", "block:", proposedBlock) + log.Debug("no consensus, now trying", "block:", proposedBlock) } } @@ -353,7 +353,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { cp.consensusGroup = consensusBackends cp.consensusGroupMux.Unlock() - log.Info("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) + log.Debug("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) } // Unban remove any bans from the backends From 92b54553c8e4b385ab55221232b9991b1a921091 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 8 May 2023 19:19:46 -0700 Subject: [PATCH 064/212] proxyd/fix: use correct context for auth --- proxyd/proxyd/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index fac04f0..2564ef6 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -576,7 +576,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context return nil } - ctx = context.WithValue(r.Context(), ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck + ctx = context.WithValue(ctx, ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck } return context.WithValue( From 32064105c8a301b496ff1c047df469caeb45d19b Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 02:57:36 -0700 Subject: [PATCH 065/212] prevent direct access to backend state struct --- proxyd/proxyd/consensus_poller.go | 33 ++++++++++++++++++------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 2dd781f..bb0cc05 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -203,23 +203,23 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller // UpdateBackend refreshes the consensus state of a single backend func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { - bs := cp.backendState[be] - if time.Now().Before(bs.bannedUntil) { - log.Debug("skipping backend banned", "backend", be.Name, "bannedUntil", bs.bannedUntil) + _, _, _, _, bannedUntil := cp.getBackendState(be) + if time.Now().Before(bannedUntil) { + log.Debug("skipping backend banned", "backend", be.Name, "bannedUntil", bannedUntil) return } // if backend it not online or not in a health state we'll only resume checkin it after ban if !be.Online() || !be.IsHealthy() { - log.Warn("backend banned - not online or not healthy", "backend", be.Name, "bannedUntil", bs.bannedUntil) - bs.bannedUntil = time.Now().Add(cp.banPeriod) + log.Warn("backend banned - not online or not healthy", "backend", be.Name, "bannedUntil", bannedUntil) + bannedUntil = time.Now().Add(cp.banPeriod) } // if backend it not in sync we'll check again after ban inSync, err := cp.isInSync(ctx, be) if err != nil || !inSync { - log.Warn("backend banned - not in sync", "backend", be.Name, "bannedUntil", bs.bannedUntil) - bs.bannedUntil = time.Now().Add(cp.banPeriod) + log.Warn("backend banned - not in sync", "backend", be.Name, "bannedUntil", bannedUntil) + bannedUntil = time.Now().Add(cp.banPeriod) } // if backend exhausted rate limit we'll skip it for now @@ -246,7 +246,11 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { if changed { RecordBackendLatestBlock(be, latestBlockNumber) - log.Debug("backend state updated", "name", be.Name, "state", bs) + log.Debug("backend state updated", + "name", be.Name, + "peerCount", peerCount, + "latestBlockNumber", latestBlockNumber, + "latestBlockHash", latestBlockHash) } } @@ -258,7 +262,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { currentConsensusBlockNumber := cp.GetConsensusBlockNumber() for _, be := range cp.backendGroup.Backends { - peerCount, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate := cp.getBackendState(be) + peerCount, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate, _ := cp.getBackendState(be) if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { continue @@ -306,10 +310,10 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - with minimum peer count - updated recently */ - bs := cp.backendState[be] - notUpdated := bs.lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) - isBanned := time.Now().Before(bs.bannedUntil) - notEnoughPeers := !be.skipPeerCountCheck && bs.peerCount < cp.minPeerCount + peerCount, _, _, lastUpdate, bannedUntil := cp.getBackendState(be) + notUpdated := lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) + isBanned := time.Now().Before(bannedUntil) + notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers { filteredBackendsNames = append(filteredBackendsNames, be.Name) continue @@ -432,13 +436,14 @@ func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bo return res, nil } -func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, blockNumber hexutil.Uint64, blockHash string, lastUpdate time.Time) { +func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, blockNumber hexutil.Uint64, blockHash string, lastUpdate time.Time, bannedUntil time.Time) { bs := cp.backendState[be] bs.backendStateMux.Lock() peerCount = bs.peerCount blockNumber = bs.latestBlockNumber blockHash = bs.latestBlockHash lastUpdate = bs.lastUpdate + bannedUntil = bs.bannedUntil bs.backendStateMux.Unlock() return } From d4b2693ffd8f99a44c88098068ddae0c2f313232 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 03:58:35 -0700 Subject: [PATCH 066/212] ban / isbanned --- proxyd/proxyd/consensus_poller.go | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index bb0cc05..34fe708 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -203,23 +203,22 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller // UpdateBackend refreshes the consensus state of a single backend func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { - _, _, _, _, bannedUntil := cp.getBackendState(be) - if time.Now().Before(bannedUntil) { - log.Debug("skipping backend banned", "backend", be.Name, "bannedUntil", bannedUntil) + if cp.IsBanned(be) { + log.Debug("skipping backend banned", "backend", be.Name) return } // if backend it not online or not in a health state we'll only resume checkin it after ban if !be.Online() || !be.IsHealthy() { - log.Warn("backend banned - not online or not healthy", "backend", be.Name, "bannedUntil", bannedUntil) - bannedUntil = time.Now().Add(cp.banPeriod) + log.Warn("backend banned - not online or not healthy", "backend", be.Name) + cp.Ban(be) } // if backend it not in sync we'll check again after ban inSync, err := cp.isInSync(ctx, be) if err != nil || !inSync { - log.Warn("backend banned - not in sync", "backend", be.Name, "bannedUntil", bannedUntil) - bannedUntil = time.Now().Add(cp.banPeriod) + log.Warn("backend banned - not in sync", "backend", be.Name) + cp.Ban(be) } // if backend exhausted rate limit we'll skip it for now @@ -363,6 +362,22 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { log.Debug("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) } +// IsBanned checks if a specific backend is banned +func (cp *ConsensusPoller) IsBanned(be *Backend) bool { + bs := cp.backendState[be] + defer bs.backendStateMux.Unlock() + bs.backendStateMux.Lock() + return time.Now().Before(bs.bannedUntil) +} + +// Ban bans a specific backend +func (cp *ConsensusPoller) Ban(be *Backend) { + bs := cp.backendState[be] + defer bs.backendStateMux.Unlock() + bs.backendStateMux.Lock() + bs.bannedUntil = time.Now().Add(cp.banPeriod) +} + // Unban remove any bans from the backends func (cp *ConsensusPoller) Unban() { for _, be := range cp.backendGroup.Backends { From cc9f6022de4416ff59dea25fa07a7594a6aa7f38 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 8 May 2023 19:59:17 -0700 Subject: [PATCH 067/212] proxyd/fix: eth2 block tags {safe, finalized} should be valid tag values and avoid cache --- proxyd/proxyd/cache_test.go | 48 ++++++++++++++++++++++++++++++------- proxyd/proxyd/methods.go | 12 ++++++++-- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/proxyd/proxyd/cache_test.go b/proxyd/proxyd/cache_test.go index 11c277b..7f7f08c 100644 --- a/proxyd/proxyd/cache_test.go +++ b/proxyd/proxyd/cache_test.go @@ -73,12 +73,48 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { Params: []byte(`["earliest", false]`), ID: ID, }, - res: &RPCRes{ + res: nil, + name: "eth_getBlockByNumber earliest", + }, + { + req: &RPCReq{ JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, + Method: "eth_getBlockByNumber", + Params: []byte(`["safe", false]`), ID: ID, }, - name: "eth_getBlockByNumber earliest", + res: nil, + name: "eth_getBlockByNumber safe", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["finalized", false]`), + ID: ID, + }, + res: nil, + name: "eth_getBlockByNumber finalized", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["pending", false]`), + ID: ID, + }, + res: nil, + name: "eth_getBlockByNumber pending", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getBlockByNumber", + Params: []byte(`["latest", false]`), + ID: ID, + }, + res: nil, + name: "eth_getBlockByNumber latest", }, { req: &RPCReq{ @@ -101,11 +137,7 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { Params: []byte(`["earliest", "0x2", false]`), ID: ID, }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, - ID: ID, - }, + res: nil, name: "eth_getBlockRange earliest", }, } diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index 4b1731f..011b476 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -271,7 +271,11 @@ func (e *EthGasPriceMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRe } func isBlockDependentParam(s string) bool { - return s == "latest" || s == "pending" + return s == "earliest" || + s == "latest" || + s == "pending" || + s == "finalized" || + s == "safe" } func decodeGetBlockByNumberParams(params json.RawMessage) (string, bool, error) { @@ -355,7 +359,11 @@ func decodeEthCallParams(req *RPCReq) (*ethCallParams, string, error) { } func validBlockInput(input string) bool { - if input == "earliest" || input == "pending" || input == "latest" { + if input == "earliest" || + input == "latest" || + input == "pending" || + input == "finalized" || + input == "safe" { return true } _, err := decodeBlockInput(input) From b95f2a6a627f2128f6da25c4ffb9e5b6ce52bbf6 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 09:38:00 -0700 Subject: [PATCH 068/212] make tag earliest cacheable --- proxyd/proxyd/methods.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index 011b476..1cba58e 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -271,8 +271,7 @@ func (e *EthGasPriceMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRe } func isBlockDependentParam(s string) bool { - return s == "earliest" || - s == "latest" || + return s == "latest" || s == "pending" || s == "finalized" || s == "safe" From 020bc662435499b5ecc219662ee4859e84ed3779 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 10:26:01 -0700 Subject: [PATCH 069/212] fix test --- proxyd/proxyd/cache_test.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/cache_test.go b/proxyd/proxyd/cache_test.go index 7f7f08c..294d0f5 100644 --- a/proxyd/proxyd/cache_test.go +++ b/proxyd/proxyd/cache_test.go @@ -73,7 +73,11 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { Params: []byte(`["earliest", false]`), ID: ID, }, - res: nil, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `{"difficulty": "0x1", "number": "0x1"}`, + ID: ID, + }, name: "eth_getBlockByNumber earliest", }, { @@ -137,7 +141,11 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { Params: []byte(`["earliest", "0x2", false]`), ID: ID, }, - res: nil, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + ID: ID, + }, name: "eth_getBlockRange earliest", }, } From 2856a2c9e1cbd07ccf72c7a3fb211a077f37ee61 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 8 May 2023 16:29:05 -0700 Subject: [PATCH 070/212] proxyd: add limit to consensus block lag --- proxyd/proxyd/config.go | 1 + proxyd/proxyd/consensus_poller.go | 21 ++++++++-- proxyd/proxyd/example.config.toml | 2 + .../integration_tests/consensus_test.go | 38 +++++++++++++++++++ .../integration_tests/testdata/consensus.toml | 1 + proxyd/proxyd/proxyd.go | 3 ++ 6 files changed, 63 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 218dbc4..d140fd3 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -105,6 +105,7 @@ type BackendGroupConfig struct { ConsensusBanPeriod TOMLDuration `toml:"consensus_ban_period"` ConsensusMaxUpdateThreshold TOMLDuration `toml:"consensus_max_update_threshold"` + ConsensusMaxBlockLag uint64 `toml:"consensus_max_block_lag"` ConsensusMinPeerCount int `toml:"consensus_min_peer_count"` } diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 34fe708..6a47cd2 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -35,6 +35,7 @@ type ConsensusPoller struct { banPeriod time.Duration maxUpdateThreshold time.Duration + maxBlockLag uint64 } type backendState struct { @@ -160,6 +161,12 @@ func WithMaxUpdateThreshold(maxUpdateThreshold time.Duration) ConsensusOpt { } } +func WithMaxBlockLag(maxBlockLag uint64) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.maxBlockLag = maxBlockLag + } +} + func WithMinPeerCount(minPeerCount uint64) ConsensusOpt { return func(cp *ConsensusPoller) { cp.minPeerCount = minPeerCount @@ -181,6 +188,7 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller banPeriod: 5 * time.Minute, maxUpdateThreshold: 30 * time.Second, + maxBlockLag: 50, minPeerCount: 3, } @@ -270,7 +278,12 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { continue } - if lowestBlock == 0 || backendLatestBlockNumber < lowestBlock { + // find the highest common ancestor, ignoring backends that are too far lagging behind + // when the backend is too far ahead from current lowest, the current lowest is ignored + // when the backend if too far behind, the backend itself is ignored + if lowestBlock == 0 || + backendLatestBlockNumber > lowestBlock && uint64(backendLatestBlockNumber-lowestBlock) > cp.maxBlockLag || + backendLatestBlockNumber < lowestBlock && uint64(lowestBlock-backendLatestBlockNumber) < cp.maxBlockLag { lowestBlock = backendLatestBlockNumber lowestBlockHash = backendLatestBlockHash } @@ -308,12 +321,14 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - not banned - with minimum peer count - updated recently + - not lagging */ - peerCount, _, _, lastUpdate, bannedUntil := cp.getBackendState(be) + peerCount, latestBlockNumber, _, lastUpdate, bannedUntil := cp.getBackendState(be) notUpdated := lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) isBanned := time.Now().Before(bannedUntil) notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount - if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers { + lagging := (latestBlockNumber < proposedBlock) && uint64(proposedBlock-latestBlockNumber) >= cp.maxBlockLag + if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers || lagging { filteredBackendsNames = append(filteredBackendsNames, be.Name) continue } diff --git a/proxyd/proxyd/example.config.toml b/proxyd/proxyd/example.config.toml index cb41614..413cd61 100644 --- a/proxyd/proxyd/example.config.toml +++ b/proxyd/proxyd/example.config.toml @@ -93,6 +93,8 @@ backends = ["infura"] # consensus_ban_period = "1m" # Maximum delay for update the backend, default 30s # consensus_max_update_threshold = "20s" +# Maximum block lag, default 50 +# consensus_max_block_lag = 10 # Minimum peer count, default 3 # consensus_min_peer_count = 4 diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 51d1f27..da162b0 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -97,6 +97,44 @@ func TestConsensus(t *testing.T) { require.Equal(t, 1, len(consensusGroup)) }) + t.Run("prevent using a backend lagging behind", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x1", "hash1"), + }) + + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x100", "hash0x100"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x100", + Response: buildGetBlockResponse("0x100", "hash0x100"), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // since we ignored node1, the consensus should be at 0x100 + require.Equal(t, "0x100", bg.Consensus.GetConsensusBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + + be := backend(bg, "node1") + require.NotNil(t, be) + require.NotContains(t, consensusGroup, be) + require.Equal(t, 1, len(consensusGroup)) + }) + t.Run("prevent using a backend not in sync", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() diff --git a/proxyd/proxyd/integration_tests/testdata/consensus.toml b/proxyd/proxyd/integration_tests/testdata/consensus.toml index d26b9dc..3f92a3d 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus.toml +++ b/proxyd/proxyd/integration_tests/testdata/consensus.toml @@ -18,6 +18,7 @@ consensus_aware = true consensus_handler = "noop" # allow more control over the consensus poller for tests consensus_ban_period = "1m" consensus_max_update_threshold = "2m" +consensus_max_block_lag = 50 consensus_min_peer_count = 4 [rpc_method_mappings] diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index cd02074..fa0371b 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -328,6 +328,9 @@ func Start(config *Config) (*Server, func(), error) { if bgcfg.ConsensusMaxUpdateThreshold > 0 { copts = append(copts, WithMaxUpdateThreshold(time.Duration(bgcfg.ConsensusMaxUpdateThreshold))) } + if bgcfg.ConsensusMaxBlockLag > 0 { + copts = append(copts, WithMaxBlockLag(bgcfg.ConsensusMaxBlockLag)) + } if bgcfg.ConsensusMinPeerCount > 0 { copts = append(copts, WithMinPeerCount(uint64(bgcfg.ConsensusMinPeerCount))) } From 3fd2abea8430a1ef8dcb568b1e69ba158fb7e522 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 8 May 2023 19:15:48 -0700 Subject: [PATCH 071/212] test on edge --- .../integration_tests/consensus_test.go | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index da162b0..02da59b 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -135,6 +135,41 @@ func TestConsensus(t *testing.T) { require.Equal(t, 1, len(consensusGroup)) }) + t.Run("prevent using a backend lagging behind 2", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x1", "hash1"), + }) + + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x33", "hash0x100"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x100", + Response: buildGetBlockResponse("0x33", "hash0x100"), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + // since we ignored node1, the consensus should be at 0x100 + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + + require.Equal(t, 2, len(consensusGroup)) + }) + t.Run("prevent using a backend not in sync", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() From 8d51c200794513de7a9b92e51a91da4c53f1cc7e Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 14:11:06 -0700 Subject: [PATCH 072/212] use a global lag comparison, non order dependent --- proxyd/proxyd/consensus_poller.go | 33 ++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 6a47cd2..6308b22 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -263,11 +263,29 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { // UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { + var highestBlock hexutil.Uint64 var lowestBlock hexutil.Uint64 var lowestBlockHash string currentConsensusBlockNumber := cp.GetConsensusBlockNumber() + // find the highest block, in order to use it defining the highest non-lagging ancestor block + for _, be := range cp.backendGroup.Backends { + peerCount, backendLatestBlockNumber, _, lastUpdate := cp.getBackendState(be) + + if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { + continue + } + if lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { + continue + } + + if backendLatestBlockNumber > highestBlock { + highestBlock = backendLatestBlockNumber + } + } + + // find the highest common ancestor block for _, be := range cp.backendGroup.Backends { peerCount, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate, _ := cp.getBackendState(be) @@ -278,12 +296,12 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { continue } - // find the highest common ancestor, ignoring backends that are too far lagging behind - // when the backend is too far ahead from current lowest, the current lowest is ignored - // when the backend if too far behind, the backend itself is ignored - if lowestBlock == 0 || - backendLatestBlockNumber > lowestBlock && uint64(backendLatestBlockNumber-lowestBlock) > cp.maxBlockLag || - backendLatestBlockNumber < lowestBlock && uint64(lowestBlock-backendLatestBlockNumber) < cp.maxBlockLag { + // check if backend is lagging behind the highest block + if backendLatestBlockNumber < highestBlock && uint64(highestBlock-backendLatestBlockNumber) > cp.maxBlockLag { + continue + } + + if lowestBlock == 0 || backendLatestBlockNumber < lowestBlock { lowestBlock = backendLatestBlockNumber lowestBlockHash = backendLatestBlockHash } @@ -323,11 +341,12 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - updated recently - not lagging */ + peerCount, latestBlockNumber, _, lastUpdate, bannedUntil := cp.getBackendState(be) notUpdated := lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) isBanned := time.Now().Before(bannedUntil) notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount - lagging := (latestBlockNumber < proposedBlock) && uint64(proposedBlock-latestBlockNumber) >= cp.maxBlockLag + lagging := latestBlockNumber < proposedBlock if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers || lagging { filteredBackendsNames = append(filteredBackendsNames, be.Name) continue From c7caf2af4dc300e8f0b104b6b40a36549f039d29 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 14:17:29 -0700 Subject: [PATCH 073/212] rebase --- proxyd/proxyd/consensus_poller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 6308b22..6290634 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -271,7 +271,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { // find the highest block, in order to use it defining the highest non-lagging ancestor block for _, be := range cp.backendGroup.Backends { - peerCount, backendLatestBlockNumber, _, lastUpdate := cp.getBackendState(be) + peerCount, backendLatestBlockNumber, _, lastUpdate, _ := cp.getBackendState(be) if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { continue From 53e4a369abe70c0fdc446fbe12d9b082f43435a2 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 14:20:23 -0700 Subject: [PATCH 074/212] test edge cases --- .../integration_tests/consensus_test.go | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 02da59b..729fade 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -135,7 +135,7 @@ func TestConsensus(t *testing.T) { require.Equal(t, 1, len(consensusGroup)) }) - t.Run("prevent using a backend lagging behind 2", func(t *testing.T) { + t.Run("prevent using a backend lagging behind - at limit", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() bg.Consensus.Unban() @@ -146,6 +146,7 @@ func TestConsensus(t *testing.T) { Response: buildGetBlockResponse("0x1", "hash1"), }) + // 0x1 + 50 = 0x33 h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", Block: "latest", @@ -170,6 +171,41 @@ func TestConsensus(t *testing.T) { require.Equal(t, 2, len(consensusGroup)) }) + t.Run("prevent using a backend lagging behind - one before limit", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x1", "hash1"), + }) + + // 0x1 + 49 = 0x32 + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x32", "hash0x100"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "0x100", + Response: buildGetBlockResponse("0x32", "hash0x100"), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + + require.Equal(t, 2, len(consensusGroup)) + }) + t.Run("prevent using a backend not in sync", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() From cde638b11dd90a07790339607be6134faf832980 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 17:21:25 -0700 Subject: [PATCH 075/212] moar consensus metrics --- proxyd/proxyd/consensus_poller.go | 22 +++++++--- proxyd/proxyd/metrics.go | 73 ++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 34fe708..a7b1fc2 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -203,7 +203,10 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller // UpdateBackend refreshes the consensus state of a single backend func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { - if cp.IsBanned(be) { + banned := cp.IsBanned(be) + RecordConsensusBackendBanned(be, banned) + + if banned { log.Debug("skipping backend banned", "backend", be.Name) return } @@ -212,6 +215,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { if !be.Online() || !be.IsHealthy() { log.Warn("backend banned - not online or not healthy", "backend", be.Name) cp.Ban(be) + return } // if backend it not in sync we'll check again after ban @@ -219,7 +223,9 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { if err != nil || !inSync { log.Warn("backend banned - not in sync", "backend", be.Name) cp.Ban(be) + return } + RecordConsensusBackendInSync(be, inSync) // if backend exhausted rate limit we'll skip it for now if be.IsRateLimited() { @@ -234,6 +240,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } } + RecordConsensusBackendPeerCount(be, peerCount) latestBlockNumber, latestBlockHash, err := cp.fetchBlock(ctx, be, "latest") if err != nil { @@ -241,15 +248,17 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - changed := cp.setBackendState(be, peerCount, latestBlockNumber, latestBlockHash) + changed, updateDelay := cp.setBackendState(be, peerCount, latestBlockNumber, latestBlockHash) if changed { RecordBackendLatestBlock(be, latestBlockNumber) + RecordConsensusBackendUpdateDelay(be, updateDelay) log.Debug("backend state updated", "name", be.Name, "peerCount", peerCount, "latestBlockNumber", latestBlockNumber, - "latestBlockHash", latestBlockHash) + "latestBlockHash", latestBlockHash, + "updateDelay", updateDelay) } } @@ -354,11 +363,13 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { } cp.tracker.SetConsensusBlockNumber(proposedBlock) - RecordGroupConsensusLatestBlock(cp.backendGroup, proposedBlock) cp.consensusGroupMux.Lock() cp.consensusGroup = consensusBackends cp.consensusGroupMux.Unlock() + RecordGroupConsensusLatestBlock(cp.backendGroup, proposedBlock) + RecordGroupConsensusCount(cp.backendGroup, len(consensusBackends)) + log.Debug("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) } @@ -463,13 +474,14 @@ func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, block return } -func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, blockNumber hexutil.Uint64, blockHash string) (changed bool) { +func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, blockNumber hexutil.Uint64, blockHash string) (changed bool, updateDelay time.Duration) { bs := cp.backendState[be] bs.backendStateMux.Lock() changed = bs.latestBlockHash != blockHash bs.peerCount = peerCount bs.latestBlockNumber = blockNumber bs.latestBlockHash = blockHash + updateDelay = time.Now().Sub(bs.lastUpdate) bs.lastUpdate = time.Now() bs.backendStateMux.Unlock() return diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 2aef49d..ab5d284 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -4,6 +4,7 @@ import ( "context" "strconv" "strings" + "time" "github.com/ethereum/go-ethereum/common/hexutil" @@ -260,6 +261,46 @@ var ( }, []string{ "backend_name", }) + + consensusGroupCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_count", + Help: "Consensus group count", + }, []string{ + "backend_group_name", + }) + + consensusBannedBackends = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "consensus_backend_banned", + Help: "Bool gauge for banned backends", + }, []string{ + "backend_name", + }) + + consensusPeerCountBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "consensus_backend_peer_count", + Help: "Peer count", + }, []string{ + "backend_name", + }) + + consensusInSyncBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "consensus_backend_in_sync", + Help: "Bool gauge for backends in sync", + }, []string{ + "backend_name", + }) + + consensusUpdateDelayBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "consensus_backend_update_delay", + Help: "Delay (ms) for backend update", + }, []string{ + "backend_name", + }) ) func RecordRedisError(source string) { @@ -321,10 +362,38 @@ func RecordBatchSize(size int) { batchSizeHistogram.Observe(float64(size)) } +func RecordGroupConsensusLatestBlock(group *BackendGroup, blockNumber hexutil.Uint64) { + consensusLatestBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) +} + +func RecordGroupConsensusCount(group *BackendGroup, count int) { + consensusGroupCount.WithLabelValues(group.Name).Set(float64(count)) +} + func RecordBackendLatestBlock(be *Backend, blockNumber hexutil.Uint64) { backendLatestBlockBackend.WithLabelValues(be.Name).Set(float64(blockNumber)) } -func RecordGroupConsensusLatestBlock(group *BackendGroup, blockNumber hexutil.Uint64) { - consensusLatestBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) +func RecordConsensusBackendBanned(be *Backend, banned bool) { + v := float64(0) + if banned { + v = float64(1) + } + consensusBannedBackends.WithLabelValues(be.Name).Set(v) +} + +func RecordConsensusBackendPeerCount(be *Backend, peerCount uint64) { + consensusPeerCountBackend.WithLabelValues(be.Name).Set(float64(peerCount)) +} + +func RecordConsensusBackendInSync(be *Backend, inSync bool) { + v := float64(0) + if inSync { + v = float64(1) + } + consensusInSyncBackend.WithLabelValues(be.Name).Set(v) +} + +func RecordConsensusBackendUpdateDelay(be *Backend, delay time.Duration) { + consensusUpdateDelayBackend.WithLabelValues(be.Name).Set(float64(delay.Round(time.Millisecond))) } From cfb26e6a8ae74e6d26bc03578af47ae7eb857a04 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 17:50:25 -0700 Subject: [PATCH 076/212] convert update delay to ms --- proxyd/proxyd/metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index ab5d284..6c76fba 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -395,5 +395,5 @@ func RecordConsensusBackendInSync(be *Backend, inSync bool) { } func RecordConsensusBackendUpdateDelay(be *Backend, delay time.Duration) { - consensusUpdateDelayBackend.WithLabelValues(be.Name).Set(float64(delay.Round(time.Millisecond))) + consensusUpdateDelayBackend.WithLabelValues(be.Name).Set(float64(delay.Milliseconds())) } From 651b526c506d0a9c213ab4a492cc33ce4a86407d Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 19:11:29 -0700 Subject: [PATCH 077/212] lint --- proxyd/proxyd/consensus_poller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index a7b1fc2..d52130c 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -481,7 +481,7 @@ func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, blockN bs.peerCount = peerCount bs.latestBlockNumber = blockNumber bs.latestBlockHash = blockHash - updateDelay = time.Now().Sub(bs.lastUpdate) + updateDelay = time.Since(bs.lastUpdate) bs.lastUpdate = time.Now() bs.backendStateMux.Unlock() return From 5f61935bc400dbf2f5e4ce4e33438dc4ca1f5a10 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 19:17:25 -0700 Subject: [PATCH 078/212] add filtered and total counts --- proxyd/proxyd/consensus_poller.go | 2 ++ proxyd/proxyd/metrics.go | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index d52130c..62f7bdf 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -369,6 +369,8 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { RecordGroupConsensusLatestBlock(cp.backendGroup, proposedBlock) RecordGroupConsensusCount(cp.backendGroup, len(consensusBackends)) + RecordGroupConsensusFilteredCount(cp.backendGroup, len(filteredBackendsNames)) + RecordGroupTotalCount(cp.backendGroup, len(cp.backendGroup.Backends)) log.Debug("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) } diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 6c76fba..efc36ac 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -265,7 +265,23 @@ var ( consensusGroupCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, Name: "group_consensus_count", - Help: "Consensus group count", + Help: "Consensus group serving traffic count", + }, []string{ + "backend_group_name", + }) + + consensusGroupFilteredCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_filtered_count", + Help: "Consensus group filtered out from serving traffic count", + }, []string{ + "backend_group_name", + }) + + consensusGroupTotalCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_total_count", + Help: "Total count of candidates to be part of consensus group", }, []string{ "backend_group_name", }) @@ -370,6 +386,14 @@ func RecordGroupConsensusCount(group *BackendGroup, count int) { consensusGroupCount.WithLabelValues(group.Name).Set(float64(count)) } +func RecordGroupConsensusFilteredCount(group *BackendGroup, count int) { + consensusGroupFilteredCount.WithLabelValues(group.Name).Set(float64(count)) +} + +func RecordGroupTotalCount(group *BackendGroup, count int) { + consensusGroupTotalCount.WithLabelValues(group.Name).Set(float64(count)) +} + func RecordBackendLatestBlock(be *Backend, blockNumber hexutil.Uint64) { backendLatestBlockBackend.WithLabelValues(be.Name).Set(float64(blockNumber)) } From 88a172f7399c0d2631640d5f0534b687ef3ba855 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 9 May 2023 19:31:49 -0700 Subject: [PATCH 079/212] skip reporting peer count according to config --- proxyd/proxyd/consensus_poller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 62f7bdf..c486d1c 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -239,8 +239,8 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { log.Warn("error updating backend", "name", be.Name, "err", err) return } + RecordConsensusBackendPeerCount(be, peerCount) } - RecordConsensusBackendPeerCount(be, peerCount) latestBlockNumber, latestBlockHash, err := cp.fetchBlock(ctx, be, "latest") if err != nil { From 8e45b5d5ca1a7202a631c66d23666725da5560eb Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 11 May 2023 15:19:52 -0700 Subject: [PATCH 080/212] feat(proxyd): prevent banning out-of-sync backend --- proxyd/proxyd/consensus_poller.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index e26d6b9..3639a25 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -215,7 +215,15 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { RecordConsensusBackendBanned(be, banned) if banned { - log.Debug("skipping backend banned", "backend", be.Name) + log.Debug("skipping backend - banned", "backend", be.Name) + return + } + + // if backend it not in sync we'll check again after ban + inSync, err := cp.isInSync(ctx, be) + RecordConsensusBackendInSync(be, err == nil && inSync) + if err != nil || !inSync { + log.Warn("skipping backend - not in sync", "backend", be.Name) return } @@ -226,15 +234,6 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - // if backend it not in sync we'll check again after ban - inSync, err := cp.isInSync(ctx, be) - if err != nil || !inSync { - log.Warn("backend banned - not in sync", "backend", be.Name) - cp.Ban(be) - return - } - RecordConsensusBackendInSync(be, inSync) - // if backend exhausted rate limit we'll skip it for now if be.IsRateLimited() { return From 5c4b805efc25bb88c4d6a5ae6013a53099315b25 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 11 May 2023 15:33:47 -0700 Subject: [PATCH 081/212] ban logic --- proxyd/proxyd/consensus_poller.go | 46 +++++++++++-------- .../integration_tests/consensus_test.go | 3 ++ 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 3639a25..d79e67a 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -44,6 +44,7 @@ type backendState struct { latestBlockNumber hexutil.Uint64 latestBlockHash string peerCount uint64 + inSync bool lastUpdate time.Time @@ -219,11 +220,9 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - // if backend it not in sync we'll check again after ban - inSync, err := cp.isInSync(ctx, be) - RecordConsensusBackendInSync(be, err == nil && inSync) - if err != nil || !inSync { - log.Warn("skipping backend - not in sync", "backend", be.Name) + // if backend exhausted rate limit we'll skip it for now + if be.IsRateLimited() { + log.Debug("skipping backend - rate limited", "backend", be.Name) return } @@ -234,17 +233,18 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - // if backend exhausted rate limit we'll skip it for now - if be.IsRateLimited() { - return + // if backend it not in sync we'll check again after ban + inSync, err := cp.isInSync(ctx, be) + RecordConsensusBackendInSync(be, err == nil && inSync) + if err != nil { + log.Warn("error updating backend sync state", "name", be.Name, "err", err) } var peerCount uint64 if !be.skipPeerCountCheck { peerCount, err = cp.getPeerCount(ctx, be) if err != nil { - log.Warn("error updating backend", "name", be.Name, "err", err) - return + log.Warn("error updating backend peer count", "name", be.Name, "err", err) } RecordConsensusBackendPeerCount(be, peerCount) } @@ -252,10 +252,9 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { latestBlockNumber, latestBlockHash, err := cp.fetchBlock(ctx, be, "latest") if err != nil { log.Warn("error updating backend", "name", be.Name, "err", err) - return } - changed, updateDelay := cp.setBackendState(be, peerCount, latestBlockNumber, latestBlockHash) + changed, updateDelay := cp.setBackendState(be, peerCount, inSync, latestBlockNumber, latestBlockHash) if changed { RecordBackendLatestBlock(be, latestBlockNumber) @@ -263,6 +262,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { log.Debug("backend state updated", "name", be.Name, "peerCount", peerCount, + "inSync", inSync, "latestBlockNumber", latestBlockNumber, "latestBlockHash", latestBlockHash, "updateDelay", updateDelay) @@ -279,11 +279,14 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { // find the highest block, in order to use it defining the highest non-lagging ancestor block for _, be := range cp.backendGroup.Backends { - peerCount, backendLatestBlockNumber, _, lastUpdate, _ := cp.getBackendState(be) + peerCount, inSync, backendLatestBlockNumber, _, lastUpdate, _ := cp.getBackendState(be) if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { continue } + if !inSync { + continue + } if lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { continue } @@ -295,11 +298,14 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { // find the highest common ancestor block for _, be := range cp.backendGroup.Backends { - peerCount, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate, _ := cp.getBackendState(be) + peerCount, inSync, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate, _ := cp.getBackendState(be) if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { continue } + if !inSync { + continue + } if lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { continue } @@ -350,12 +356,12 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - not lagging */ - peerCount, latestBlockNumber, _, lastUpdate, bannedUntil := cp.getBackendState(be) + peerCount, inSync, latestBlockNumber, _, lastUpdate, bannedUntil := cp.getBackendState(be) notUpdated := lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) isBanned := time.Now().Before(bannedUntil) notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount lagging := latestBlockNumber < proposedBlock - if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers || lagging { + if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers || lagging || !inSync { filteredBackendsNames = append(filteredBackendsNames, be.Name) continue } @@ -497,23 +503,25 @@ func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bo return res, nil } -func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, blockNumber hexutil.Uint64, blockHash string, lastUpdate time.Time, bannedUntil time.Time) { +func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, inSync bool, blockNumber hexutil.Uint64, blockHash string, lastUpdate time.Time, bannedUntil time.Time) { bs := cp.backendState[be] + defer bs.backendStateMux.Unlock() bs.backendStateMux.Lock() peerCount = bs.peerCount + inSync = bs.inSync blockNumber = bs.latestBlockNumber blockHash = bs.latestBlockHash lastUpdate = bs.lastUpdate bannedUntil = bs.bannedUntil - bs.backendStateMux.Unlock() return } -func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, blockNumber hexutil.Uint64, blockHash string) (changed bool, updateDelay time.Duration) { +func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync bool, blockNumber hexutil.Uint64, blockHash string) (changed bool, updateDelay time.Duration) { bs := cp.backendState[be] bs.backendStateMux.Lock() changed = bs.latestBlockHash != blockHash bs.peerCount = peerCount + bs.inSync = inSync bs.latestBlockNumber = blockNumber bs.latestBlockHash = blockHash updateDelay = time.Since(bs.lastUpdate) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 729fade..41f7e22 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -94,6 +94,7 @@ func TestConsensus(t *testing.T) { consensusGroup := bg.Consensus.GetConsensusGroup() require.NotContains(t, consensusGroup, be) + require.False(t, bg.Consensus.IsBanned(be)) require.Equal(t, 1, len(consensusGroup)) }) @@ -132,6 +133,7 @@ func TestConsensus(t *testing.T) { be := backend(bg, "node1") require.NotNil(t, be) require.NotContains(t, consensusGroup, be) + require.False(t, bg.Consensus.IsBanned(be)) require.Equal(t, 1, len(consensusGroup)) }) @@ -232,6 +234,7 @@ func TestConsensus(t *testing.T) { consensusGroup := bg.Consensus.GetConsensusGroup() require.NotContains(t, consensusGroup, be) + require.False(t, bg.Consensus.IsBanned(be)) require.Equal(t, 1, len(consensusGroup)) }) From e025bcc4b4b8c3a457d784023f59302b39ff3057 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 26 Apr 2023 16:31:03 -0700 Subject: [PATCH 082/212] proxyd: emit event on consensus broken --- proxyd/proxyd/cache.go | 33 +++++++++++++++++++ proxyd/proxyd/consensus_poller.go | 18 +++++++++- .../integration_tests/consensus_test.go | 7 +++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 73b7fd8..3a3731e 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -12,6 +12,7 @@ import ( type Cache interface { Get(ctx context.Context, key string) (string, error) Put(ctx context.Context, key string, value string) error + Clear(ctx context.Context) error } const ( @@ -42,6 +43,11 @@ func (c *cache) Put(ctx context.Context, key string, value string) error { return nil } +func (c *cache) Clear(ctx context.Context) error { + c.lru.Purge() + return nil +} + type redisCache struct { rdb *redis.Client } @@ -75,6 +81,29 @@ func (c *redisCache) Put(ctx context.Context, key string, value string) error { return err } +func (c *redisCache) Clear(ctx context.Context) error { + patterns := []string{"lvc:*", "method:*"} + + for _, p := range patterns { + scmd := c.rdb.Keys(ctx, p) + err := scmd.Err() + if err != nil { + RecordRedisError("CacheClear") + return err + } + keys, _ := scmd.Result() + + icmd := c.rdb.Del(ctx, keys...) + err = icmd.Err() + if err != nil { + RecordRedisError("CacheClear") + return err + } + } + + return nil +} + type cacheWithCompression struct { cache Cache } @@ -103,6 +132,10 @@ func (c *cacheWithCompression) Put(ctx context.Context, key string, value string return c.cache.Put(ctx, key, string(encodedVal)) } +func (c *cacheWithCompression) Clear(ctx context.Context) error { + return c.cache.Clear(ctx) +} + type GetLatestBlockNumFn func(ctx context.Context) (uint64, error) type GetLatestGasPriceFn func(ctx context.Context) (uint64, error) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index e26d6b9..b4d3b20 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -17,11 +17,14 @@ const ( PollerInterval = 1 * time.Second ) +type OnConsensusBroken func() + // ConsensusPoller checks the consensus state for each member of a BackendGroup // resolves the highest common block for multiple nodes, and reconciles the consensus // in case of block hash divergence to minimize re-orgs type ConsensusPoller struct { cancelFunc context.CancelFunc + listeners []OnConsensusBroken backendGroup *BackendGroup backendState map[*Backend]*backendState @@ -149,6 +152,16 @@ func WithAsyncHandler(asyncHandler ConsensusAsyncHandler) ConsensusOpt { } } +func WithListener(listener OnConsensusBroken) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.AddListener(listener) + } +} + +func (cp *ConsensusPoller) AddListener(listener OnConsensusBroken) { + cp.listeners = append(cp.listeners, listener) +} + func WithBanPeriod(banPeriod time.Duration) ConsensusOpt { return func(cp *ConsensusPoller) { cp.banPeriod = banPeriod @@ -349,6 +362,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - with minimum peer count - updated recently - not lagging + - in sync */ peerCount, latestBlockNumber, _, lastUpdate, bannedUntil := cp.getBackendState(be) @@ -392,7 +406,9 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { } if broken { - // propagate event to other interested parts, such as cache invalidator + for _, l := range cp.listeners { + l() + } log.Info("consensus broken", "currentConsensusBlockNumber", currentConsensusBlockNumber, "proposedBlock", proposedBlock, "proposedBlockHash", proposedBlockHash) } diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 729fade..59f3664 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -286,6 +286,11 @@ func TestConsensus(t *testing.T) { h2.ResetOverrides() bg.Consensus.Unban() + listenerCalled := false + bg.Consensus.AddListener(func() { + listenerCalled = true + }) + for _, be := range bg.Backends { bg.Consensus.UpdateBackend(ctx, be) } @@ -331,7 +336,7 @@ func TestConsensus(t *testing.T) { // should resolve to 0x1, since 0x2 is out of consensus at the moment require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) - // later, when impl events, listen to broken consensus event + require.True(t, listenerCalled) }) t.Run("broken consensus with depth 2", func(t *testing.T) { From 491369d32f695455f16d11cb70e6874da07dc08f Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 11 May 2023 15:44:12 -0700 Subject: [PATCH 083/212] lint --- proxyd/proxyd/consensus_poller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index b4d3b20..fbb8a17 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -362,7 +362,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - with minimum peer count - updated recently - not lagging - - in sync + - in sync */ peerCount, latestBlockNumber, _, lastUpdate, bannedUntil := cp.getBackendState(be) From ef42dde6e1c41b17d777435ab8d5a80ce9152a29 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 11 May 2023 15:45:33 -0700 Subject: [PATCH 084/212] revert cache.go --- proxyd/proxyd/cache.go | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 3a3731e..73b7fd8 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -12,7 +12,6 @@ import ( type Cache interface { Get(ctx context.Context, key string) (string, error) Put(ctx context.Context, key string, value string) error - Clear(ctx context.Context) error } const ( @@ -43,11 +42,6 @@ func (c *cache) Put(ctx context.Context, key string, value string) error { return nil } -func (c *cache) Clear(ctx context.Context) error { - c.lru.Purge() - return nil -} - type redisCache struct { rdb *redis.Client } @@ -81,29 +75,6 @@ func (c *redisCache) Put(ctx context.Context, key string, value string) error { return err } -func (c *redisCache) Clear(ctx context.Context) error { - patterns := []string{"lvc:*", "method:*"} - - for _, p := range patterns { - scmd := c.rdb.Keys(ctx, p) - err := scmd.Err() - if err != nil { - RecordRedisError("CacheClear") - return err - } - keys, _ := scmd.Result() - - icmd := c.rdb.Del(ctx, keys...) - err = icmd.Err() - if err != nil { - RecordRedisError("CacheClear") - return err - } - } - - return nil -} - type cacheWithCompression struct { cache Cache } @@ -132,10 +103,6 @@ func (c *cacheWithCompression) Put(ctx context.Context, key string, value string return c.cache.Put(ctx, key, string(encodedVal)) } -func (c *cacheWithCompression) Clear(ctx context.Context) error { - return c.cache.Clear(ctx) -} - type GetLatestBlockNumFn func(ctx context.Context) (uint64, error) type GetLatestGasPriceFn func(ctx context.Context) (uint64, error) From bae755b825f11e938702b44a389e829aa9ead1fa Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 13 May 2023 22:19:32 -0700 Subject: [PATCH 085/212] fix(proxyd): add missing shutdown for consensus poller --- proxyd/proxyd/server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 2564ef6..b52224c 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -222,6 +222,11 @@ func (s *Server) Shutdown() { if s.wsServer != nil { _ = s.wsServer.Shutdown(context.Background()) } + for _, bg := range s.BackendGroups { + if bg.Consensus != nil { + bg.Consensus.Shutdown() + } + } } func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request) { From 29f7aa88ab91b9f8332bc7a0de637048f92b42a8 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 13 May 2023 22:33:09 -0700 Subject: [PATCH 086/212] feat(proxyd): redis namespace --- proxyd/proxyd/cache.go | 19 ++++++++++++++----- proxyd/proxyd/config.go | 3 ++- .../integration_tests/testdata/caching.toml | 1 + proxyd/proxyd/proxyd.go | 2 +- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 73b7fd8..9e399df 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -2,6 +2,7 @@ package proxyd import ( "context" + "strings" "time" "github.com/go-redis/redis/v8" @@ -43,16 +44,24 @@ func (c *cache) Put(ctx context.Context, key string, value string) error { } type redisCache struct { - rdb *redis.Client + rdb *redis.Client + prefix string } -func newRedisCache(rdb *redis.Client) *redisCache { - return &redisCache{rdb} +func newRedisCache(rdb *redis.Client, prefix string) *redisCache { + return &redisCache{rdb, prefix} +} + +func (c *redisCache) namespaced(key string) string { + if c.prefix == "" { + return key + } + return strings.Join([]string{c.prefix, key}, ":") } func (c *redisCache) Get(ctx context.Context, key string) (string, error) { start := time.Now() - val, err := c.rdb.Get(ctx, key).Result() + val, err := c.rdb.Get(ctx, c.namespaced(key)).Result() redisCacheDurationSumm.WithLabelValues("GET").Observe(float64(time.Since(start).Milliseconds())) if err == redis.Nil { @@ -66,7 +75,7 @@ func (c *redisCache) Get(ctx context.Context, key string) (string, error) { func (c *redisCache) Put(ctx context.Context, key string, value string) error { start := time.Now() - err := c.rdb.SetEX(ctx, key, value, redisTTL).Err() + err := c.rdb.SetEX(ctx, c.namespaced(key), value, redisTTL).Err() redisCacheDurationSumm.WithLabelValues("SETEX").Observe(float64(time.Since(start).Milliseconds())) if err != nil { diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index d140fd3..4f36931 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -32,7 +32,8 @@ type CacheConfig struct { } type RedisConfig struct { - URL string `toml:"url"` + URL string `toml:"url"` + Namespace string `toml:"namespace"` } type MetricsConfig struct { diff --git a/proxyd/proxyd/integration_tests/testdata/caching.toml b/proxyd/proxyd/integration_tests/testdata/caching.toml index cd14ff3..ef9d6de 100644 --- a/proxyd/proxyd/integration_tests/testdata/caching.toml +++ b/proxyd/proxyd/integration_tests/testdata/caching.toml @@ -6,6 +6,7 @@ response_timeout_seconds = 1 [redis] url = "$REDIS_URL" +namespace = "proxyd" [cache] enabled = true diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index fa0371b..4fc286e 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -236,7 +236,7 @@ func Start(config *Config) (*Server, func(), error) { log.Warn("redis is not configured, using in-memory cache") cache = newMemoryCache() } else { - cache = newRedisCache(redisClient) + cache = newRedisCache(redisClient, config.Redis.Namespace) } // Ideally, the BlocKSyncRPCURL should be the sequencer or a HA replica that's not far behind ethClient, err := ethclient.Dial(blockSyncRPCURL) From 490624b25e2a60ef8e9ad894b029690f2c948847 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 15 May 2023 19:20:08 -0700 Subject: [PATCH 087/212] refactor(proxy): cache only immutable and hash-based methods --- proxyd/proxyd/cache.go | 32 +- proxyd/proxyd/cache_test.go | 620 +++--------------- .../proxyd/integration_tests/caching_test.go | 103 ++- .../integration_tests/testdata/caching.toml | 7 + proxyd/proxyd/methods.go | 393 +---------- proxyd/proxyd/metrics.go | 12 + proxyd/proxyd/proxyd.go | 21 +- 7 files changed, 234 insertions(+), 954 deletions(-) diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 73b7fd8..59b8cef 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -116,15 +116,17 @@ type rpcCache struct { handlers map[string]RPCMethodHandler } -func newRPCCache(cache Cache, getLatestBlockNumFn GetLatestBlockNumFn, getLatestGasPriceFn GetLatestGasPriceFn, numBlockConfirmations int) RPCCache { +func newRPCCache(cache Cache) RPCCache { handlers := map[string]RPCMethodHandler{ - "eth_chainId": &StaticMethodHandler{}, - "net_version": &StaticMethodHandler{}, - "eth_getBlockByNumber": &EthGetBlockByNumberMethodHandler{cache, getLatestBlockNumFn, numBlockConfirmations}, - "eth_getBlockRange": &EthGetBlockRangeMethodHandler{cache, getLatestBlockNumFn, numBlockConfirmations}, - "eth_blockNumber": &EthBlockNumberMethodHandler{getLatestBlockNumFn}, - "eth_gasPrice": &EthGasPriceMethodHandler{getLatestGasPriceFn}, - "eth_call": &EthCallMethodHandler{cache, getLatestBlockNumFn, numBlockConfirmations}, + "eth_chainId": &StaticMethodHandler{cache: cache}, + "net_version": &StaticMethodHandler{cache: cache}, + "eth_getBlockTransactionCountByHash": &StaticMethodHandler{cache: cache}, + "eth_getUncleCountByBlockHash": &StaticMethodHandler{cache: cache}, + "eth_getBlockByHash": &StaticMethodHandler{cache: cache}, + "eth_getTransactionByHash": &StaticMethodHandler{cache: cache}, + "eth_getTransactionByBlockHashAndIndex": &StaticMethodHandler{cache: cache}, + "eth_getUncleByBlockHashAndIndex": &StaticMethodHandler{cache: cache}, + "eth_getTransactionReceipt": &StaticMethodHandler{cache: cache}, } return &rpcCache{ cache: cache, @@ -138,12 +140,14 @@ func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) { return nil, nil } res, err := handler.GetRPCMethod(ctx, req) - if res != nil { - if res == nil { - RecordCacheMiss(req.Method) - } else { - RecordCacheHit(req.Method) - } + if err != nil { + RecordCacheError(req.Method) + return nil, err + } + if res == nil { + RecordCacheMiss(req.Method) + } else { + RecordCacheHit(req.Method) } return res, err } diff --git a/proxyd/proxyd/cache_test.go b/proxyd/proxyd/cache_test.go index 294d0f5..0deb0e3 100644 --- a/proxyd/proxyd/cache_test.go +++ b/proxyd/proxyd/cache_test.go @@ -2,23 +2,16 @@ package proxyd import ( "context" - "math" "strconv" "testing" "github.com/stretchr/testify/require" ) -const numBlockConfirmations = 10 - func TestRPCCacheImmutableRPCs(t *testing.T) { - const blockHead = math.MaxUint64 ctx := context.Background() - getBlockNum := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), getBlockNum, nil, numBlockConfirmations) + cache := newRPCCache(newMemoryCache()) ID := []byte(strconv.Itoa(1)) rpcs := []struct { @@ -55,98 +48,100 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { { req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["0x1", false]`), + Method: "eth_getBlockTransactionCountByHash", + Params: mustMarshalJSON([]string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}), ID: ID, }, res: &RPCRes{ JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, + Result: `{"eth_getBlockTransactionCountByHash":"!"}`, ID: ID, }, - name: "eth_getBlockByNumber", + name: "eth_getBlockTransactionCountByHash", }, { req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["earliest", false]`), + Method: "eth_getUncleCountByBlockHash", + Params: mustMarshalJSON([]string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}), ID: ID, }, res: &RPCRes{ JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, + Result: `{"eth_getUncleCountByBlockHash":"!"}`, ID: ID, }, - name: "eth_getBlockByNumber earliest", + name: "eth_getUncleCountByBlockHash", }, { req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["safe", false]`), - ID: ID, - }, - res: nil, - name: "eth_getBlockByNumber safe", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["finalized", false]`), - ID: ID, - }, - res: nil, - name: "eth_getBlockByNumber finalized", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["pending", false]`), - ID: ID, - }, - res: nil, - name: "eth_getBlockByNumber pending", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["latest", false]`), - ID: ID, - }, - res: nil, - name: "eth_getBlockByNumber latest", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "0x2", false]`), + Method: "eth_getBlockByHash", + Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}), ID: ID, }, res: &RPCRes{ JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + Result: `{"eth_getBlockByHash":"!"}`, ID: ID, }, - name: "eth_getBlockRange", + name: "eth_getBlockByHash", }, { req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["earliest", "0x2", false]`), + Method: "eth_getTransactionByHash", + Params: mustMarshalJSON([]string{"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"}), ID: ID, }, res: &RPCRes{ JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + Result: `{"eth_getTransactionByHash":"!"}`, ID: ID, }, - name: "eth_getBlockRange earliest", + name: "eth_getTransactionByHash", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getTransactionByBlockHashAndIndex", + Params: mustMarshalJSON([]string{"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", "0x55"}), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `{"eth_getTransactionByBlockHashAndIndex":"!"}`, + ID: ID, + }, + name: "eth_getTransactionByBlockHashAndIndex", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getUncleByBlockHashAndIndex", + Params: mustMarshalJSON([]string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238", "0x90"}), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `{"eth_getUncleByBlockHashAndIndex":"!"}`, + ID: ID, + }, + name: "eth_getUncleByBlockHashAndIndex", + }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_getTransactionReceipt", + Params: mustMarshalJSON([]string{"0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5"}), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `{"eth_getTransactionReceipt":"!"}`, + ID: ID, + }, + name: "eth_getTransactionReceipt", }, } @@ -162,215 +157,70 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { } } -func TestRPCCacheBlockNumber(t *testing.T) { - var blockHead uint64 = 0x1000 - var gasPrice uint64 = 0x100 - ctx := context.Background() - ID := []byte(strconv.Itoa(1)) - - getGasPrice := func(ctx context.Context) (uint64, error) { - return gasPrice, nil - } - getBlockNum := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), getBlockNum, getGasPrice, numBlockConfirmations) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_blockNumber", - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `0x1000`, - ID: ID, - } - - err := cache.PutRPC(ctx, req, res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - - blockHead = 0x1001 - cachedRes, err = cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, &RPCRes{JSONRPC: "2.0", Result: `0x1001`, ID: ID}, cachedRes) -} - -func TestRPCCacheGasPrice(t *testing.T) { - var blockHead uint64 = 0x1000 - var gasPrice uint64 = 0x100 - ctx := context.Background() - ID := []byte(strconv.Itoa(1)) - - getGasPrice := func(ctx context.Context) (uint64, error) { - return gasPrice, nil - } - getBlockNum := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), getBlockNum, getGasPrice, numBlockConfirmations) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_gasPrice", - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `0x100`, - ID: ID, - } - - err := cache.PutRPC(ctx, req, res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - - gasPrice = 0x101 - cachedRes, err = cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, &RPCRes{JSONRPC: "2.0", Result: `0x101`, ID: ID}, cachedRes) -} - func TestRPCCacheUnsupportedMethod(t *testing.T) { - const blockHead = math.MaxUint64 ctx := context.Background() - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) - ID := []byte(strconv.Itoa(1)) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_syncing", - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: false, - ID: ID, - } - - err := cache.PutRPC(ctx, req, res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) -} - -func TestRPCCacheEthGetBlockByNumber(t *testing.T) { - ctx := context.Background() - - var blockHead uint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - makeCache := func() RPCCache { return newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) } - ID := []byte(strconv.Itoa(1)) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["0xa", false]`), - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, - ID: ID, - } - req2 := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["0xb", false]`), - ID: ID, - } - res2 := &RPCRes{ - JSONRPC: "2.0", - Result: `{"difficulty": "0x2", "number": "0x2"}`, - ID: ID, - } - - t.Run("set multiple finalized blocks", func(t *testing.T) { - blockHead = 100 - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - require.NoError(t, cache.PutRPC(ctx, req2, res2)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - cachedRes, err = cache.GetRPC(ctx, req2) - require.NoError(t, err) - require.Equal(t, res2, cachedRes) - }) - - t.Run("unconfirmed block", func(t *testing.T) { - blockHead = 0xc - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) -} - -func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) { - ctx := context.Background() - - var blockHead uint64 = 2 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) + cache := newRPCCache(newMemoryCache()) ID := []byte(strconv.Itoa(1)) rpcs := []struct { req *RPCReq - res *RPCRes name string }{ { + name: "eth_syncing", + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_syncing", + ID: ID, + }, + }, + { + name: "eth_blockNumber", + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_blockNumber", + ID: ID, + }, + }, + { + name: "eth_getBlockByNumber", req: &RPCReq{ JSONRPC: "2.0", Method: "eth_getBlockByNumber", - Params: []byte(`["latest", false]`), ID: ID, }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, - ID: ID, - }, - name: "latest block", }, { + name: "eth_getBlockRange", req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["pending", false]`), + Method: "eth_getBlockRange", ID: ID, }, - res: &RPCRes{ + }, + { + name: "eth_gasPrice", + req: &RPCReq{ JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, + Method: "eth_gasPrice", + ID: ID, + }, + }, + { + name: "eth_call", + req: &RPCReq{ + JSONRPC: "2.0", + Method: "eth_gasPrice", ID: ID, }, - name: "pending block", }, } for _, rpc := range rpcs { t.Run(rpc.name, func(t *testing.T) { - err := cache.PutRPC(ctx, rpc.req, rpc.res) + fakeval := mustMarshalJSON([]string{rpc.name}) + err := cache.PutRPC(ctx, rpc.req, &RPCRes{Result: fakeval}) require.NoError(t, err) cachedRes, err := cache.GetRPC(ctx, rpc.req) @@ -378,285 +228,5 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) { require.Nil(t, cachedRes) }) } -} - -func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) { - ctx := context.Background() - - const blockHead = math.MaxUint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) - ID := []byte(strconv.Itoa(1)) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["0x1"]`), // missing required boolean param - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, - ID: ID, - } - - err := cache.PutRPC(ctx, req, res) - require.Error(t, err) - - cachedRes, err := cache.GetRPC(ctx, req) - require.Error(t, err) - require.Nil(t, cachedRes) -} - -func TestRPCCacheEthGetBlockRange(t *testing.T) { - ctx := context.Background() - - var blockHead uint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - makeCache := func() RPCCache { return newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) } - ID := []byte(strconv.Itoa(1)) - - t.Run("finalized block", func(t *testing.T) { - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "0x10", false]`), - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x10"}]`, - ID: ID, - } - blockHead = 0x1000 - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - }) - - t.Run("unconfirmed block", func(t *testing.T) { - cache := makeCache() - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "0x1000", false]`), - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, - ID: ID, - } - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) -} - -func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) { - ctx := context.Background() - - var blockHead uint64 = 0x1000 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) - ID := []byte(strconv.Itoa(1)) - - rpcs := []struct { - req *RPCReq - res *RPCRes - name string - }{ - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "latest", false]`), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, - ID: ID, - }, - name: "latest block", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "pending", false]`), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, - ID: ID, - }, - name: "pending block", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["latest", "0x1000", false]`), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, - ID: ID, - }, - name: "latest block 2", - }, - } - - for _, rpc := range rpcs { - t.Run(rpc.name, func(t *testing.T) { - err := cache.PutRPC(ctx, rpc.req, rpc.res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, rpc.req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) - } -} - -func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) { - ctx := context.Background() - - const blockHead = math.MaxUint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) - ID := []byte(strconv.Itoa(1)) - - rpcs := []struct { - req *RPCReq - res *RPCRes - name string - }{ - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "0x2"]`), // missing required boolean param - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, - ID: ID, - }, - name: "missing boolean param", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["abc", "0x2", true]`), // invalid block hex - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, - ID: ID, - }, - name: "invalid block hex", - }, - } - - for _, rpc := range rpcs { - t.Run(rpc.name, func(t *testing.T) { - err := cache.PutRPC(ctx, rpc.req, rpc.res) - require.Error(t, err) - - cachedRes, err := cache.GetRPC(ctx, rpc.req) - require.Error(t, err) - require.Nil(t, cachedRes) - }) - } -} - -func TestRPCCacheEthCall(t *testing.T) { - ctx := context.Background() - - var blockHead uint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - - makeCache := func() RPCCache { return newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) } - ID := []byte(strconv.Itoa(1)) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_call", - Params: []byte(`[{"to": "0xDEADBEEF", "data": "0x1"}, "0x10"]`), - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `0x0`, - ID: ID, - } - - t.Run("finalized block", func(t *testing.T) { - blockHead = 0x100 - cache := makeCache() - err := cache.PutRPC(ctx, req, res) - require.NoError(t, err) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - }) - - t.Run("unconfirmed block", func(t *testing.T) { - blockHead = 0x10 - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) - - t.Run("latest block", func(t *testing.T) { - blockHead = 0x100 - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_call", - Params: []byte(`[{"to": "0xDEADBEEF", "data": "0x1"}, "latest"]`), - ID: ID, - } - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) - - t.Run("pending block", func(t *testing.T) { - blockHead = 0x100 - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_call", - Params: []byte(`[{"to": "0xDEADBEEF", "data": "0x1"}, "pending"]`), - ID: ID, - } - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) + } diff --git a/proxyd/proxyd/integration_tests/caching_test.go b/proxyd/proxyd/integration_tests/caching_test.go index b0a2f20..a69ec4f 100644 --- a/proxyd/proxyd/integration_tests/caching_test.go +++ b/proxyd/proxyd/integration_tests/caching_test.go @@ -18,15 +18,19 @@ func TestCaching(t *testing.T) { defer redis.Close() hdlr := NewBatchRPCResponseRouter() + /* cacheable */ hdlr.SetRoute("eth_chainId", "999", "0x420") hdlr.SetRoute("net_version", "999", "0x1234") - hdlr.SetRoute("eth_blockNumber", "999", "0x64") - hdlr.SetRoute("eth_getBlockByNumber", "999", "dummy_block") - hdlr.SetRoute("eth_call", "999", "dummy_call") - - // mock LVC requests - hdlr.SetFallbackRoute("eth_blockNumber", "0x64") - hdlr.SetFallbackRoute("eth_gasPrice", "0x420") + hdlr.SetRoute("eth_getBlockTransactionCountByHash", "999", "eth_getBlockTransactionCountByHash") + hdlr.SetRoute("eth_getBlockByHash", "999", "eth_getBlockByHash") + hdlr.SetRoute("eth_getTransactionByHash", "999", "eth_getTransactionByHash") + hdlr.SetRoute("eth_getTransactionByBlockHashAndIndex", "999", "eth_getTransactionByBlockHashAndIndex") + hdlr.SetRoute("eth_getUncleByBlockHashAndIndex", "999", "eth_getUncleByBlockHashAndIndex") + hdlr.SetRoute("eth_getTransactionReceipt", "999", "eth_getTransactionReceipt") + /* not cacheable */ + hdlr.SetRoute("eth_getBlockByNumber", "999", "eth_getBlockByNumber") + hdlr.SetRoute("eth_blockNumber", "999", "eth_blockNumber") + hdlr.SetRoute("eth_call", "999", "eth_call") backend := NewMockBackend(hdlr) defer backend.Close() @@ -48,6 +52,7 @@ func TestCaching(t *testing.T) { response string backendCalls int }{ + /* cacheable */ { "eth_chainId", nil, @@ -60,14 +65,51 @@ func TestCaching(t *testing.T) { "{\"jsonrpc\": \"2.0\", \"result\": \"0x1234\", \"id\": 999}", 1, }, + { + "eth_getBlockTransactionCountByHash", + []interface{}{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockTransactionCountByHash\", \"id\": 999}", + 1, + }, + { + "eth_getBlockByHash", + []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByHash\", \"id\": 999}", + 1, + }, + { + "eth_getTransactionByHash", + []interface{}{"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionByHash\", \"id\": 999}", + 1, + }, + { + "eth_getTransactionByBlockHashAndIndex", + []interface{}{"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", "0x55"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionByBlockHashAndIndex\", \"id\": 999}", + 1, + }, + { + "eth_getUncleByBlockHashAndIndex", + []interface{}{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238", "0x90"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getUncleByBlockHashAndIndex\", \"id\": 999}", + 1, + }, + { + "eth_getTransactionReceipt", + []interface{}{"0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionReceipt\", \"id\": 999}", + 1, + }, + /* not cacheable */ { "eth_getBlockByNumber", []interface{}{ "0x1", true, }, - "{\"jsonrpc\": \"2.0\", \"result\": \"dummy_block\", \"id\": 999}", - 1, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByNumber\", \"id\": 999}", + 2, }, { "eth_call", @@ -79,14 +121,14 @@ func TestCaching(t *testing.T) { }, "0x60", }, - "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}", - 1, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_call\", \"id\": 999}", + 2, }, { "eth_blockNumber", nil, - "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"0x64\"}", - 0, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_blockNumber\", \"id\": 999}", + 2, }, { "eth_call", @@ -98,7 +140,7 @@ func TestCaching(t *testing.T) { }, "latest", }, - "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}", + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_call\", \"id\": 999}", 2, }, { @@ -111,7 +153,7 @@ func TestCaching(t *testing.T) { }, "pending", }, - "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}", + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_call\", \"id\": 999}", 2, }, } @@ -128,24 +170,15 @@ func TestCaching(t *testing.T) { }) } - t.Run("block numbers update", func(t *testing.T) { - hdlr.SetFallbackRoute("eth_blockNumber", "0x100") - time.Sleep(1500 * time.Millisecond) - resRaw, _, err := client.SendRPC("eth_blockNumber", nil) - require.NoError(t, err) - RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"0x100\"}"), resRaw) - backend.Reset() - }) - t.Run("nil responses should not be cached", func(t *testing.T) { - hdlr.SetRoute("eth_getBlockByNumber", "999", nil) - resRaw, _, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x123"}) + hdlr.SetRoute("eth_getBlockByHash", "999", nil) + resRaw, _, err := client.SendRPC("eth_getBlockByHash", []interface{}{"0x123"}) require.NoError(t, err) - resCache, _, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x123"}) + resCache, _, err := client.SendRPC("eth_getBlockByHash", []interface{}{"0x123"}) require.NoError(t, err) RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":null}"), resRaw) RequireEqualJSON(t, resRaw, resCache) - require.Equal(t, 2, countRequests(backend, "eth_getBlockByNumber")) + require.Equal(t, 2, countRequests(backend, "eth_getBlockByHash")) }) } @@ -158,10 +191,7 @@ func TestBatchCaching(t *testing.T) { hdlr.SetRoute("eth_chainId", "1", "0x420") hdlr.SetRoute("net_version", "1", "0x1234") hdlr.SetRoute("eth_call", "1", "dummy_call") - - // mock LVC requests - hdlr.SetFallbackRoute("eth_blockNumber", "0x64") - hdlr.SetFallbackRoute("eth_gasPrice", "0x420") + hdlr.SetRoute("eth_getBlockByHash", "1", "eth_getBlockByHash") backend := NewMockBackend(hdlr) defer backend.Close() @@ -181,26 +211,31 @@ func TestBatchCaching(t *testing.T) { goodChainIdResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"0x420\", \"id\": 1}" goodNetVersionResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"0x1234\", \"id\": 1}" goodEthCallResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"dummy_call\", \"id\": 1}" + goodEthGetBlockByHash := "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByHash\", \"id\": 1}" res, _, err := client.SendBatchRPC( NewRPCReq("1", "eth_chainId", nil), NewRPCReq("1", "net_version", nil), + NewRPCReq("1", "eth_getBlockByHash", []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}), ) require.NoError(t, err) - RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodNetVersionResponse)), res) + RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodNetVersionResponse, goodEthGetBlockByHash)), res) require.Equal(t, 1, countRequests(backend, "eth_chainId")) require.Equal(t, 1, countRequests(backend, "net_version")) + require.Equal(t, 1, countRequests(backend, "eth_getBlockByHash")) backend.Reset() res, _, err = client.SendBatchRPC( NewRPCReq("1", "eth_chainId", nil), NewRPCReq("1", "eth_call", []interface{}{`{"to":"0x1234"}`, "pending"}), NewRPCReq("1", "net_version", nil), + NewRPCReq("1", "eth_getBlockByHash", []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}), ) require.NoError(t, err) - RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodEthCallResponse, goodNetVersionResponse)), res) + RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodEthCallResponse, goodNetVersionResponse, goodEthGetBlockByHash)), res) require.Equal(t, 0, countRequests(backend, "eth_chainId")) require.Equal(t, 0, countRequests(backend, "net_version")) + require.Equal(t, 0, countRequests(backend, "eth_getBlockByHash")) require.Equal(t, 1, countRequests(backend, "eth_call")) } diff --git a/proxyd/proxyd/integration_tests/testdata/caching.toml b/proxyd/proxyd/integration_tests/testdata/caching.toml index cd14ff3..530d220 100644 --- a/proxyd/proxyd/integration_tests/testdata/caching.toml +++ b/proxyd/proxyd/integration_tests/testdata/caching.toml @@ -27,3 +27,10 @@ net_version = "main" eth_getBlockByNumber = "main" eth_blockNumber = "main" eth_call = "main" +eth_getBlockTransactionCountByHash = "main" +eth_getUncleCountByBlockHash = "main" +eth_getBlockByHash = "main" +eth_getTransactionByHash = "main" +eth_getTransactionByBlockHashAndIndex = "main" +eth_getUncleByBlockHashAndIndex = "main" +eth_getTransactionReceipt = "main" diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index 1cba58e..82f6138 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -3,15 +3,9 @@ package proxyd import ( "context" "encoding/json" - "errors" - "fmt" + "github.com/ethereum/go-ethereum/log" + "strings" "sync" - - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var ( - errInvalidRPCParams = errors.New("invalid RPC params") ) type RPCMethodHandler interface { @@ -20,366 +14,27 @@ type RPCMethodHandler interface { } type StaticMethodHandler struct { - cache interface{} + cache Cache m sync.RWMutex } +func (e *StaticMethodHandler) key(req *RPCReq) string { + // signature is a cache-friendly base64-encoded string with json.RawMessage param contents + signature := string(req.Params) + return strings.Join([]string{"cache", req.Method, signature}, ":") +} + func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - e.m.RLock() - cache := e.cache - e.m.RUnlock() - - if cache == nil { - return nil, nil - } - return &RPCRes{ - JSONRPC: req.JSONRPC, - Result: cache, - ID: req.ID, - }, nil -} - -func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { - e.m.Lock() if e.cache == nil { - e.cache = res.Result - } - e.m.Unlock() - return nil -} - -type EthGetBlockByNumberMethodHandler struct { - cache Cache - getLatestBlockNumFn GetLatestBlockNumFn - numBlockConfirmations int -} - -func (e *EthGetBlockByNumberMethodHandler) cacheKey(req *RPCReq) string { - input, includeTx, err := decodeGetBlockByNumberParams(req.Params) - if err != nil { - return "" - } - return fmt.Sprintf("method:eth_getBlockByNumber:%s:%t", input, includeTx) -} - -func (e *EthGetBlockByNumberMethodHandler) cacheable(req *RPCReq) (bool, error) { - blockNum, _, err := decodeGetBlockByNumberParams(req.Params) - if err != nil { - return false, err - } - return !isBlockDependentParam(blockNum), nil -} - -func (e *EthGetBlockByNumberMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - if ok, err := e.cacheable(req); !ok || err != nil { - return nil, err - } - key := e.cacheKey(req) - return getImmutableRPCResponse(ctx, e.cache, key, req) -} - -func (e *EthGetBlockByNumberMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { - if ok, err := e.cacheable(req); !ok || err != nil { - return err - } - - blockInput, _, err := decodeGetBlockByNumberParams(req.Params) - if err != nil { - return err - } - if isBlockDependentParam(blockInput) { - return nil - } - if blockInput != "earliest" { - curBlock, err := e.getLatestBlockNumFn(ctx) - if err != nil { - return err - } - blockNum, err := decodeBlockInput(blockInput) - if err != nil { - return err - } - if curBlock <= blockNum+uint64(e.numBlockConfirmations) { - return nil - } - } - - key := e.cacheKey(req) - return putImmutableRPCResponse(ctx, e.cache, key, req, res) -} - -type EthGetBlockRangeMethodHandler struct { - cache Cache - getLatestBlockNumFn GetLatestBlockNumFn - numBlockConfirmations int -} - -func (e *EthGetBlockRangeMethodHandler) cacheKey(req *RPCReq) string { - start, end, includeTx, err := decodeGetBlockRangeParams(req.Params) - if err != nil { - return "" - } - return fmt.Sprintf("method:eth_getBlockRange:%s:%s:%t", start, end, includeTx) -} - -func (e *EthGetBlockRangeMethodHandler) cacheable(req *RPCReq) (bool, error) { - start, end, _, err := decodeGetBlockRangeParams(req.Params) - if err != nil { - return false, err - } - return !isBlockDependentParam(start) && !isBlockDependentParam(end), nil -} - -func (e *EthGetBlockRangeMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - if ok, err := e.cacheable(req); !ok || err != nil { - return nil, err - } - - key := e.cacheKey(req) - return getImmutableRPCResponse(ctx, e.cache, key, req) -} - -func (e *EthGetBlockRangeMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { - if ok, err := e.cacheable(req); !ok || err != nil { - return err - } - - start, end, _, err := decodeGetBlockRangeParams(req.Params) - if err != nil { - return err - } - curBlock, err := e.getLatestBlockNumFn(ctx) - if err != nil { - return err - } - if start != "earliest" { - startNum, err := decodeBlockInput(start) - if err != nil { - return err - } - if curBlock <= startNum+uint64(e.numBlockConfirmations) { - return nil - } - } - if end != "earliest" { - endNum, err := decodeBlockInput(end) - if err != nil { - return err - } - if curBlock <= endNum+uint64(e.numBlockConfirmations) { - return nil - } - } - - key := e.cacheKey(req) - return putImmutableRPCResponse(ctx, e.cache, key, req, res) -} - -type EthCallMethodHandler struct { - cache Cache - getLatestBlockNumFn GetLatestBlockNumFn - numBlockConfirmations int -} - -func (e *EthCallMethodHandler) cacheable(params *ethCallParams, blockTag string) bool { - if isBlockDependentParam(blockTag) { - return false - } - if params.From != "" || params.Gas != "" { - return false - } - if params.Value != "" && params.Value != "0x0" { - return false - } - return true -} - -func (e *EthCallMethodHandler) cacheKey(params *ethCallParams, blockTag string) string { - keyParams := fmt.Sprintf("%s:%s:%s", params.To, params.Data, blockTag) - return fmt.Sprintf("method:eth_call:%s", keyParams) -} - -func (e *EthCallMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - params, blockTag, err := decodeEthCallParams(req) - if err != nil { - return nil, err - } - if !e.cacheable(params, blockTag) { return nil, nil } - key := e.cacheKey(params, blockTag) - return getImmutableRPCResponse(ctx, e.cache, key, req) -} + e.m.RLock() + defer e.m.RUnlock() -func (e *EthCallMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { - params, blockTag, err := decodeEthCallParams(req) - if err != nil { - return err - } - if !e.cacheable(params, blockTag) { - return nil - } - - if blockTag != "earliest" { - curBlock, err := e.getLatestBlockNumFn(ctx) - if err != nil { - return err - } - blockNum, err := decodeBlockInput(blockTag) - if err != nil { - return err - } - if curBlock <= blockNum+uint64(e.numBlockConfirmations) { - return nil - } - } - - key := e.cacheKey(params, blockTag) - return putImmutableRPCResponse(ctx, e.cache, key, req, res) -} - -type EthBlockNumberMethodHandler struct { - getLatestBlockNumFn GetLatestBlockNumFn -} - -func (e *EthBlockNumberMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - blockNum, err := e.getLatestBlockNumFn(ctx) - if err != nil { - return nil, err - } - return makeRPCRes(req, hexutil.EncodeUint64(blockNum)), nil -} - -func (e *EthBlockNumberMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRes) error { - return nil -} - -type EthGasPriceMethodHandler struct { - getLatestGasPrice GetLatestGasPriceFn -} - -func (e *EthGasPriceMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - gasPrice, err := e.getLatestGasPrice(ctx) - if err != nil { - return nil, err - } - return makeRPCRes(req, hexutil.EncodeUint64(gasPrice)), nil -} - -func (e *EthGasPriceMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRes) error { - return nil -} - -func isBlockDependentParam(s string) bool { - return s == "latest" || - s == "pending" || - s == "finalized" || - s == "safe" -} - -func decodeGetBlockByNumberParams(params json.RawMessage) (string, bool, error) { - var list []interface{} - if err := json.Unmarshal(params, &list); err != nil { - return "", false, err - } - if len(list) != 2 { - return "", false, errInvalidRPCParams - } - blockNum, ok := list[0].(string) - if !ok { - return "", false, errInvalidRPCParams - } - includeTx, ok := list[1].(bool) - if !ok { - return "", false, errInvalidRPCParams - } - if !validBlockInput(blockNum) { - return "", false, errInvalidRPCParams - } - return blockNum, includeTx, nil -} - -func decodeGetBlockRangeParams(params json.RawMessage) (string, string, bool, error) { - var list []interface{} - if err := json.Unmarshal(params, &list); err != nil { - return "", "", false, err - } - if len(list) != 3 { - return "", "", false, errInvalidRPCParams - } - startBlockNum, ok := list[0].(string) - if !ok { - return "", "", false, errInvalidRPCParams - } - endBlockNum, ok := list[1].(string) - if !ok { - return "", "", false, errInvalidRPCParams - } - includeTx, ok := list[2].(bool) - if !ok { - return "", "", false, errInvalidRPCParams - } - if !validBlockInput(startBlockNum) || !validBlockInput(endBlockNum) { - return "", "", false, errInvalidRPCParams - } - return startBlockNum, endBlockNum, includeTx, nil -} - -func decodeBlockInput(input string) (uint64, error) { - return hexutil.DecodeUint64(input) -} - -type ethCallParams struct { - From string `json:"from"` - To string `json:"to"` - Gas string `json:"gas"` - GasPrice string `json:"gasPrice"` - Value string `json:"value"` - Data string `json:"data"` -} - -func decodeEthCallParams(req *RPCReq) (*ethCallParams, string, error) { - var input []json.RawMessage - if err := json.Unmarshal(req.Params, &input); err != nil { - return nil, "", err - } - if len(input) != 2 { - return nil, "", fmt.Errorf("invalid eth_call parameters") - } - params := new(ethCallParams) - if err := json.Unmarshal(input[0], params); err != nil { - return nil, "", err - } - var blockTag string - if err := json.Unmarshal(input[1], &blockTag); err != nil { - return nil, "", err - } - return params, blockTag, nil -} - -func validBlockInput(input string) bool { - if input == "earliest" || - input == "latest" || - input == "pending" || - input == "finalized" || - input == "safe" { - return true - } - _, err := decodeBlockInput(input) - return err == nil -} - -func makeRPCRes(req *RPCReq, result interface{}) *RPCRes { - return &RPCRes{ - JSONRPC: JSONRPCVersion, - ID: req.ID, - Result: result, - } -} - -func getImmutableRPCResponse(ctx context.Context, cache Cache, key string, req *RPCReq) (*RPCRes, error) { - val, err := cache.Get(ctx, key) + key := e.key(req) + val, err := e.cache.Get(ctx, key) if err != nil { + log.Error("error reading from cache", "key", key, "method", req.Method, "err", err) return nil, err } if val == "" { @@ -388,6 +43,7 @@ func getImmutableRPCResponse(ctx context.Context, cache Cache, key string, req * var result interface{} if err := json.Unmarshal([]byte(val), &result); err != nil { + log.Error("error unmarshalling value from cache", "key", key, "method", req.Method, "err", err) return nil, err } return &RPCRes{ @@ -397,10 +53,21 @@ func getImmutableRPCResponse(ctx context.Context, cache Cache, key string, req * }, nil } -func putImmutableRPCResponse(ctx context.Context, cache Cache, key string, req *RPCReq, res *RPCRes) error { - if key == "" { +func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { + if e.cache == nil { return nil } - val := mustMarshalJSON(res.Result) - return cache.Put(ctx, key, string(val)) + + e.m.Lock() + defer e.m.Unlock() + + key := e.key(req) + value := mustMarshalJSON(res.Result) + + err := e.cache.Put(ctx, key, string(value)) + if err != nil { + log.Error("error marshalling value to put into cache", "key", key, "method", req.Method, "err", err) + return err + } + return nil } diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index efc36ac..66c1358 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -182,6 +182,14 @@ var ( "method", }) + cacheErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "cache_errors_total", + Help: "Number of cache errors.", + }, []string{ + "method", + }) + lvcErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: MetricsNamespace, Name: "lvc_errors_total", @@ -374,6 +382,10 @@ func RecordCacheMiss(method string) { cacheMissesTotal.WithLabelValues(method).Inc() } +func RecordCacheError(method string) { + cacheMissesTotal.WithLabelValues(method).Inc() +} + func RecordBatchSize(size int) { batchSizeHistogram.Observe(float64(size)) } diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index fa0371b..a49f84e 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -213,17 +213,10 @@ func Start(config *Config) (*Server, func(), error) { } var ( - rpcCache RPCCache - blockNumLVC *EthLastValueCache - gasPriceLVC *EthLastValueCache + cache Cache + rpcCache RPCCache ) if config.Cache.Enabled { - var ( - cache Cache - blockNumFn GetLatestBlockNumFn - gasPriceFn GetLatestGasPriceFn - ) - if config.Cache.BlockSyncRPCURL == "" { return nil, nil, fmt.Errorf("block sync node required for caching") } @@ -245,9 +238,7 @@ func Start(config *Config) (*Server, func(), error) { } defer ethClient.Close() - blockNumLVC, blockNumFn = makeGetLatestBlockNumFn(ethClient, cache) - gasPriceLVC, gasPriceFn = makeGetLatestGasPriceFn(ethClient, cache) - rpcCache = newRPCCache(newCacheWithCompression(cache), blockNumFn, gasPriceFn, config.Cache.NumBlockConfirmations) + rpcCache = newRPCCache(newCacheWithCompression(cache)) } srv, err := NewServer( @@ -345,12 +336,6 @@ func Start(config *Config) (*Server, func(), error) { shutdownFunc := func() { log.Info("shutting down proxyd") - if blockNumLVC != nil { - blockNumLVC.Stop() - } - if gasPriceLVC != nil { - gasPriceLVC.Stop() - } srv.Shutdown() if err := lim.FlushBackendWSConns(backendNames); err != nil { log.Error("error flushing backend ws conns", "err", err) From af481985faee0e68259bb7be6e0dc655f9e3a739 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 15 May 2023 19:30:03 -0700 Subject: [PATCH 088/212] lint --- proxyd/proxyd/lvc.go | 87 ---------------------------------------- proxyd/proxyd/methods.go | 3 +- proxyd/proxyd/metrics.go | 16 -------- proxyd/proxyd/proxyd.go | 38 ------------------ 4 files changed, 2 insertions(+), 142 deletions(-) delete mode 100644 proxyd/proxyd/lvc.go diff --git a/proxyd/proxyd/lvc.go b/proxyd/proxyd/lvc.go deleted file mode 100644 index 146bbce..0000000 --- a/proxyd/proxyd/lvc.go +++ /dev/null @@ -1,87 +0,0 @@ -package proxyd - -import ( - "context" - "time" - - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" -) - -const cacheSyncRate = 1 * time.Second - -type lvcUpdateFn func(context.Context, *ethclient.Client) (string, error) - -type EthLastValueCache struct { - client *ethclient.Client - cache Cache - key string - updater lvcUpdateFn - quit chan struct{} -} - -func newLVC(client *ethclient.Client, cache Cache, cacheKey string, updater lvcUpdateFn) *EthLastValueCache { - return &EthLastValueCache{ - client: client, - cache: cache, - key: cacheKey, - updater: updater, - quit: make(chan struct{}), - } -} - -func (h *EthLastValueCache) Start() { - go func() { - ticker := time.NewTicker(cacheSyncRate) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - lvcPollTimeGauge.WithLabelValues(h.key).SetToCurrentTime() - - value, err := h.getUpdate() - if err != nil { - log.Error("error retrieving latest value", "key", h.key, "error", err) - continue - } - log.Trace("polling latest value", "value", value) - - if err := h.cache.Put(context.Background(), h.key, value); err != nil { - log.Error("error writing last value to cache", "key", h.key, "error", err) - } - - case <-h.quit: - return - } - } - }() -} - -func (h *EthLastValueCache) getUpdate() (string, error) { - const maxRetries = 5 - var err error - - for i := 0; i <= maxRetries; i++ { - var value string - value, err = h.updater(context.Background(), h.client) - if err != nil { - backoff := calcBackoff(i) - log.Warn("http operation failed. retrying...", "error", err, "backoff", backoff) - lvcErrorsTotal.WithLabelValues(h.key).Inc() - time.Sleep(backoff) - continue - } - return value, nil - } - - return "", wrapErr(err, "exceeded retries") -} - -func (h *EthLastValueCache) Stop() { - close(h.quit) -} - -func (h *EthLastValueCache) Read(ctx context.Context) (string, error) { - return h.cache.Get(ctx, h.key) -} diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index 82f6138..43f93c5 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -3,9 +3,10 @@ package proxyd import ( "context" "encoding/json" - "github.com/ethereum/go-ethereum/log" "strings" "sync" + + "github.com/ethereum/go-ethereum/log" ) type RPCMethodHandler interface { diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 66c1358..891cc9a 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -190,22 +190,6 @@ var ( "method", }) - lvcErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "lvc_errors_total", - Help: "Count of lvc errors.", - }, []string{ - "key", - }) - - lvcPollTimeGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "lvc_poll_time_gauge", - Help: "Gauge of lvc poll time.", - }, []string{ - "key", - }) - batchRPCShortCircuitsTotal = promauto.NewCounter(prometheus.CounterOpts{ Namespace: MetricsNamespace, Name: "batch_rpc_short_circuits_total", diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index a49f84e..15fd916 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -1,13 +1,11 @@ package proxyd import ( - "context" "crypto/tls" "errors" "fmt" "net/http" "os" - "strconv" "time" "github.com/ethereum/go-ethereum/common/math" @@ -370,39 +368,3 @@ func configureBackendTLS(cfg *BackendConfig) (*tls.Config, error) { return tlsConfig, nil } - -func makeUint64LastValueFn(client *ethclient.Client, cache Cache, key string, updater lvcUpdateFn) (*EthLastValueCache, func(context.Context) (uint64, error)) { - lvc := newLVC(client, cache, key, updater) - lvc.Start() - return lvc, func(ctx context.Context) (uint64, error) { - value, err := lvc.Read(ctx) - if err != nil { - return 0, err - } - if value == "" { - return 0, fmt.Errorf("%s is unavailable", key) - } - valueUint, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return 0, err - } - return valueUint, nil - } -} - -func makeGetLatestBlockNumFn(client *ethclient.Client, cache Cache) (*EthLastValueCache, GetLatestBlockNumFn) { - return makeUint64LastValueFn(client, cache, "lvc:block_number", func(ctx context.Context, c *ethclient.Client) (string, error) { - blockNum, err := c.BlockNumber(ctx) - return strconv.FormatUint(blockNum, 10), err - }) -} - -func makeGetLatestGasPriceFn(client *ethclient.Client, cache Cache) (*EthLastValueCache, GetLatestGasPriceFn) { - return makeUint64LastValueFn(client, cache, "lvc:gas_price", func(ctx context.Context, c *ethclient.Client) (string, error) { - gasPrice, err := c.SuggestGasPrice(ctx) - if err != nil { - return "", err - } - return gasPrice.String(), nil - }) -} From 7db23916248af3412ddcec899c7cdbea098f5469 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 15 May 2023 19:38:09 -0700 Subject: [PATCH 089/212] fix metric copypasta --- proxyd/proxyd/metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 891cc9a..3420b71 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -367,7 +367,7 @@ func RecordCacheMiss(method string) { } func RecordCacheError(method string) { - cacheMissesTotal.WithLabelValues(method).Inc() + cacheErrorsTotal.WithLabelValues(method).Inc() } func RecordBatchSize(size int) { From 9757609d61abea35da7074166a2978dd5c3380e9 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 15 May 2023 20:20:42 -0700 Subject: [PATCH 090/212] nits --- proxyd/proxyd/cache.go | 24 +++++++++++------------- proxyd/proxyd/methods.go | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 59b8cef..08a69b2 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -103,9 +103,6 @@ func (c *cacheWithCompression) Put(ctx context.Context, key string, value string return c.cache.Put(ctx, key, string(encodedVal)) } -type GetLatestBlockNumFn func(ctx context.Context) (uint64, error) -type GetLatestGasPriceFn func(ctx context.Context) (uint64, error) - type RPCCache interface { GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error @@ -117,16 +114,17 @@ type rpcCache struct { } func newRPCCache(cache Cache) RPCCache { + staticHandler := &StaticMethodHandler{cache: cache} handlers := map[string]RPCMethodHandler{ - "eth_chainId": &StaticMethodHandler{cache: cache}, - "net_version": &StaticMethodHandler{cache: cache}, - "eth_getBlockTransactionCountByHash": &StaticMethodHandler{cache: cache}, - "eth_getUncleCountByBlockHash": &StaticMethodHandler{cache: cache}, - "eth_getBlockByHash": &StaticMethodHandler{cache: cache}, - "eth_getTransactionByHash": &StaticMethodHandler{cache: cache}, - "eth_getTransactionByBlockHashAndIndex": &StaticMethodHandler{cache: cache}, - "eth_getUncleByBlockHashAndIndex": &StaticMethodHandler{cache: cache}, - "eth_getTransactionReceipt": &StaticMethodHandler{cache: cache}, + "eth_chainId": staticHandler, + "net_version": staticHandler, + "eth_getBlockTransactionCountByHash": staticHandler, + "eth_getUncleCountByBlockHash": staticHandler, + "eth_getBlockByHash": staticHandler, + "eth_getTransactionByHash": staticHandler, + "eth_getTransactionByBlockHashAndIndex": staticHandler, + "eth_getUncleByBlockHashAndIndex": staticHandler, + "eth_getTransactionReceipt": staticHandler, } return &rpcCache{ cache: cache, @@ -149,7 +147,7 @@ func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) { } else { RecordCacheHit(req.Method) } - return res, err + return res, nil } func (c *rpcCache) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error { diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index 43f93c5..528e8fc 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -67,7 +67,7 @@ func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res err := e.cache.Put(ctx, key, string(value)) if err != nil { - log.Error("error marshalling value to put into cache", "key", key, "method", req.Method, "err", err) + log.Error("error putting into cache", "key", key, "method", req.Method, "err", err) return err } return nil From 2bf0517c3f1346695e7d851df58a8d74ac6e542f Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 15 May 2023 20:50:28 -0700 Subject: [PATCH 091/212] signature should be base64 --- proxyd/proxyd/methods.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index 528e8fc..3ca32a5 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -2,6 +2,7 @@ package proxyd import ( "context" + "encoding/base64" "encoding/json" "strings" "sync" @@ -21,7 +22,7 @@ type StaticMethodHandler struct { func (e *StaticMethodHandler) key(req *RPCReq) string { // signature is a cache-friendly base64-encoded string with json.RawMessage param contents - signature := string(req.Params) + signature := base64.StdEncoding.EncodeToString(req.Params) return strings.Join([]string{"cache", req.Method, signature}, ":") } From 8aadf7abb1e711bc986522f8cef99fc48d1327c8 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 17 May 2023 21:38:29 -0700 Subject: [PATCH 092/212] replace base64 encoding for params with sha256 hash --- proxyd/proxyd/methods.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index 3ca32a5..60c0595 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -2,8 +2,9 @@ package proxyd import ( "context" - "encoding/base64" + "crypto/sha256" "encoding/json" + "fmt" "strings" "sync" @@ -21,8 +22,10 @@ type StaticMethodHandler struct { } func (e *StaticMethodHandler) key(req *RPCReq) string { - // signature is a cache-friendly base64-encoded string with json.RawMessage param contents - signature := base64.StdEncoding.EncodeToString(req.Params) + // signature is the hashed json.RawMessage param contents + h := sha256.New() + h.Write(req.Params) + signature := fmt.Sprintf("%x", h.Sum(nil)) return strings.Join([]string{"cache", req.Method, signature}, ":") } From 2f0f949784b8fc77b94e3d9ab8b3be460141954c Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 13 May 2023 20:54:27 -0700 Subject: [PATCH 093/212] refactor(proxyd): clean up backend rate limiter --- proxyd/proxyd/backend.go | 91 ------ proxyd/proxyd/backend_rate_limiter.go | 286 ------------------ proxyd/proxyd/consensus_poller.go | 19 +- .../proxyd/integration_tests/failover_test.go | 13 +- .../integration_tests/rate_limit_test.go | 17 -- proxyd/proxyd/integration_tests/ws_test.go | 29 -- proxyd/proxyd/proxyd.go | 24 +- proxyd/proxyd/server.go | 10 + 8 files changed, 25 insertions(+), 464 deletions(-) delete mode 100644 proxyd/proxyd/backend_rate_limiter.go diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 9244315..4e8fd70 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -121,7 +121,6 @@ type Backend struct { wsURL string authUsername string authPassword string - rateLimiter BackendRateLimiter client *LimitedHTTPClient dialer *websocket.Dialer maxRetries int @@ -243,7 +242,6 @@ func NewBackend( name string, rpcURL string, wsURL string, - rateLimiter BackendRateLimiter, rpcSemaphore *semaphore.Weighted, opts ...BackendOpt, ) *Backend { @@ -251,7 +249,6 @@ func NewBackend( Name: name, rpcURL: rpcURL, wsURL: wsURL, - rateLimiter: rateLimiter, maxResponseSize: math.MaxInt64, client: &LimitedHTTPClient{ Client: http.Client{Timeout: 5 * time.Second}, @@ -281,15 +278,6 @@ func NewBackend( } func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { - if !b.Online() { - RecordBatchRPCError(ctx, b.Name, reqs, ErrBackendOffline) - return nil, ErrBackendOffline - } - if b.IsRateLimited() { - RecordBatchRPCError(ctx, b.Name, reqs, ErrBackendOverCapacity) - return nil, ErrBackendOverCapacity - } - var lastError error // <= to account for the first attempt not technically being // a retry @@ -340,24 +328,12 @@ func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([] return res, err } - b.setOffline() return nil, wrapErr(lastError, "permanent error forwarding request") } func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { - if !b.Online() { - return nil, ErrBackendOffline - } - if b.IsWSSaturated() { - return nil, ErrBackendOverCapacity - } - backendConn, _, err := b.dialer.Dial(b.wsURL, nil) // nolint:bodyclose if err != nil { - b.setOffline() - if err := b.rateLimiter.DecBackendWSConns(b.Name); err != nil { - log.Error("error decrementing backend ws conns", "name", b.Name, "err", err) - } return nil, wrapErr(err, "error dialing backend") } @@ -365,66 +341,6 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet return NewWSProxier(b, clientConn, backendConn, methodWhitelist), nil } -func (b *Backend) Online() bool { - online, err := b.rateLimiter.IsBackendOnline(b.Name) - if err != nil { - log.Warn( - "error getting backend availability, assuming it is offline", - "name", b.Name, - "err", err, - ) - return false - } - return online -} - -func (b *Backend) IsRateLimited() bool { - if b.maxRPS == 0 { - return false - } - - usedLimit, err := b.rateLimiter.IncBackendRPS(b.Name) - if err != nil { - log.Error( - "error getting backend used rate limit, assuming limit is exhausted", - "name", b.Name, - "err", err, - ) - return true - } - - return b.maxRPS < usedLimit -} - -func (b *Backend) IsWSSaturated() bool { - if b.maxWSConns == 0 { - return false - } - - incremented, err := b.rateLimiter.IncBackendWSConns(b.Name, b.maxWSConns) - if err != nil { - log.Error( - "error getting backend used ws conns, assuming limit is exhausted", - "name", b.Name, - "err", err, - ) - return true - } - - return !incremented -} - -func (b *Backend) setOffline() { - err := b.rateLimiter.SetBackendOffline(b.Name, b.outOfServiceInterval) - if err != nil { - log.Warn( - "error setting backend offline", - "name", b.Name, - "err", err, - ) - } -} - // ForwardRPC makes a call directly to a backend and populate the response into `res` func (b *Backend) ForwardRPC(ctx context.Context, res *RPCRes, id string, method string, params ...any) error { jsonParams, err := json.Marshal(params) @@ -968,9 +884,6 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { func (w *WSProxier) close() { w.clientConn.Close() w.backendConn.Close() - if err := w.backend.rateLimiter.DecBackendWSConns(w.backend.Name); err != nil { - log.Error("error decrementing backend ws conns", "name", w.backend.Name, "err", err) - } activeBackendWsConnsGauge.WithLabelValues(w.backend.Name).Dec() } @@ -984,10 +897,6 @@ func (w *WSProxier) prepareClientMsg(msg []byte) (*RPCReq, error) { return req, ErrMethodNotWhitelisted } - if w.backend.IsRateLimited() { - return req, ErrBackendOverCapacity - } - return req, nil } diff --git a/proxyd/proxyd/backend_rate_limiter.go b/proxyd/proxyd/backend_rate_limiter.go deleted file mode 100644 index 3cc6fae..0000000 --- a/proxyd/proxyd/backend_rate_limiter.go +++ /dev/null @@ -1,286 +0,0 @@ -package proxyd - -import ( - "context" - "crypto/rand" - "encoding/hex" - "fmt" - "math" - "sync" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" -) - -const MaxRPSScript = ` -local current -current = redis.call("incr", KEYS[1]) -if current == 1 then - redis.call("expire", KEYS[1], 1) -end -return current -` - -const MaxConcurrentWSConnsScript = ` -redis.call("sadd", KEYS[1], KEYS[2]) -local total = 0 -local scanres = redis.call("sscan", KEYS[1], 0) -for _, k in ipairs(scanres[2]) do - local value = redis.call("get", k) - if value then - total = total + value - end -end - -if total < tonumber(ARGV[1]) then - redis.call("incr", KEYS[2]) - redis.call("expire", KEYS[2], 300) - return true -end - -return false -` - -type BackendRateLimiter interface { - IsBackendOnline(name string) (bool, error) - SetBackendOffline(name string, duration time.Duration) error - IncBackendRPS(name string) (int, error) - IncBackendWSConns(name string, max int) (bool, error) - DecBackendWSConns(name string) error - FlushBackendWSConns(names []string) error -} - -type RedisBackendRateLimiter struct { - rdb *redis.Client - randID string - touchKeys map[string]time.Duration - tkMtx sync.Mutex -} - -func NewRedisRateLimiter(rdb *redis.Client) BackendRateLimiter { - out := &RedisBackendRateLimiter{ - rdb: rdb, - randID: randStr(20), - touchKeys: make(map[string]time.Duration), - } - go out.touch() - return out -} - -func (r *RedisBackendRateLimiter) IsBackendOnline(name string) (bool, error) { - exists, err := r.rdb.Exists(context.Background(), fmt.Sprintf("backend:%s:offline", name)).Result() - if err != nil { - RecordRedisError("IsBackendOnline") - return false, wrapErr(err, "error getting backend availability") - } - - return exists == 0, nil -} - -func (r *RedisBackendRateLimiter) SetBackendOffline(name string, duration time.Duration) error { - if duration == 0 { - return nil - } - err := r.rdb.SetEX( - context.Background(), - fmt.Sprintf("backend:%s:offline", name), - 1, - duration, - ).Err() - if err != nil { - RecordRedisError("SetBackendOffline") - return wrapErr(err, "error setting backend unavailable") - } - return nil -} - -func (r *RedisBackendRateLimiter) IncBackendRPS(name string) (int, error) { - cmd := r.rdb.Eval( - context.Background(), - MaxRPSScript, - []string{fmt.Sprintf("backend:%s:ratelimit", name)}, - ) - rps, err := cmd.Int() - if err != nil { - RecordRedisError("IncBackendRPS") - return -1, wrapErr(err, "error upserting backend rate limit") - } - return rps, nil -} - -func (r *RedisBackendRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { - connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) - r.tkMtx.Lock() - r.touchKeys[connsKey] = 5 * time.Minute - r.tkMtx.Unlock() - cmd := r.rdb.Eval( - context.Background(), - MaxConcurrentWSConnsScript, - []string{ - fmt.Sprintf("backend:%s:proxies", name), - connsKey, - }, - max, - ) - incremented, err := cmd.Bool() - // false gets coerced to redis.nil, see https://redis.io/commands/eval#conversion-between-lua-and-redis-data-types - if err == redis.Nil { - return false, nil - } - if err != nil { - RecordRedisError("IncBackendWSConns") - return false, wrapErr(err, "error incrementing backend ws conns") - } - return incremented, nil -} - -func (r *RedisBackendRateLimiter) DecBackendWSConns(name string) error { - connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) - err := r.rdb.Decr(context.Background(), connsKey).Err() - if err != nil { - RecordRedisError("DecBackendWSConns") - return wrapErr(err, "error decrementing backend ws conns") - } - return nil -} - -func (r *RedisBackendRateLimiter) FlushBackendWSConns(names []string) error { - ctx := context.Background() - for _, name := range names { - connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) - err := r.rdb.SRem( - ctx, - fmt.Sprintf("backend:%s:proxies", name), - connsKey, - ).Err() - if err != nil { - return wrapErr(err, "error flushing backend ws conns") - } - err = r.rdb.Del(ctx, connsKey).Err() - if err != nil { - return wrapErr(err, "error flushing backend ws conns") - } - } - return nil -} - -func (r *RedisBackendRateLimiter) touch() { - for { - r.tkMtx.Lock() - for key, dur := range r.touchKeys { - if err := r.rdb.Expire(context.Background(), key, dur).Err(); err != nil { - RecordRedisError("touch") - log.Error("error touching redis key", "key", key, "err", err) - } - } - r.tkMtx.Unlock() - time.Sleep(5 * time.Second) - } -} - -type LocalBackendRateLimiter struct { - deadBackends map[string]time.Time - backendRPS map[string]int - backendWSConns map[string]int - mtx sync.RWMutex -} - -func NewLocalBackendRateLimiter() *LocalBackendRateLimiter { - out := &LocalBackendRateLimiter{ - deadBackends: make(map[string]time.Time), - backendRPS: make(map[string]int), - backendWSConns: make(map[string]int), - } - go out.clear() - return out -} - -func (l *LocalBackendRateLimiter) IsBackendOnline(name string) (bool, error) { - l.mtx.RLock() - defer l.mtx.RUnlock() - return l.deadBackends[name].Before(time.Now()), nil -} - -func (l *LocalBackendRateLimiter) SetBackendOffline(name string, duration time.Duration) error { - l.mtx.Lock() - defer l.mtx.Unlock() - l.deadBackends[name] = time.Now().Add(duration) - return nil -} - -func (l *LocalBackendRateLimiter) IncBackendRPS(name string) (int, error) { - l.mtx.Lock() - defer l.mtx.Unlock() - l.backendRPS[name] += 1 - return l.backendRPS[name], nil -} - -func (l *LocalBackendRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { - l.mtx.Lock() - defer l.mtx.Unlock() - if l.backendWSConns[name] == max { - return false, nil - } - l.backendWSConns[name] += 1 - return true, nil -} - -func (l *LocalBackendRateLimiter) DecBackendWSConns(name string) error { - l.mtx.Lock() - defer l.mtx.Unlock() - if l.backendWSConns[name] == 0 { - return nil - } - l.backendWSConns[name] -= 1 - return nil -} - -func (l *LocalBackendRateLimiter) FlushBackendWSConns(names []string) error { - return nil -} - -func (l *LocalBackendRateLimiter) clear() { - for { - time.Sleep(time.Second) - l.mtx.Lock() - l.backendRPS = make(map[string]int) - l.mtx.Unlock() - } -} - -func randStr(l int) string { - b := make([]byte, l) - if _, err := rand.Read(b); err != nil { - panic(err) - } - return hex.EncodeToString(b) -} - -type NoopBackendRateLimiter struct{} - -var noopBackendRateLimiter = &NoopBackendRateLimiter{} - -func (n *NoopBackendRateLimiter) IsBackendOnline(name string) (bool, error) { - return true, nil -} - -func (n *NoopBackendRateLimiter) SetBackendOffline(name string, duration time.Duration) error { - return nil -} - -func (n *NoopBackendRateLimiter) IncBackendRPS(name string) (int, error) { - return math.MaxInt, nil -} - -func (n *NoopBackendRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { - return true, nil -} - -func (n *NoopBackendRateLimiter) DecBackendWSConns(name string) error { - return nil -} - -func (n *NoopBackendRateLimiter) FlushBackendWSConns(names []string) error { - return nil -} diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index f077a0f..a170aca 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -233,14 +233,8 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - // if backend exhausted rate limit we'll skip it for now - if be.IsRateLimited() { - log.Debug("skipping backend - rate limited", "backend", be.Name) - return - } - - // if backend it not online or not in a health state we'll only resume checkin it after ban - if !be.Online() || !be.IsHealthy() { + // if backend is not healthy state we'll only resume checking it after ban + if !be.IsHealthy() { log.Warn("backend banned - not online or not healthy", "backend", be.Name) cp.Ban(be) return @@ -361,12 +355,10 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { /* a serving node needs to be: - healthy (network) - - not rate limited - - online + - updated recently - not banned - with minimum peer count - - updated recently - - not lagging + - not lagging latest block - in sync */ @@ -375,7 +367,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { isBanned := time.Now().Before(bannedUntil) notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount lagging := latestBlockNumber < proposedBlock - if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers || lagging || !inSync { + if !be.IsHealthy() || notUpdated || isBanned || notEnoughPeers || lagging || !inSync { filteredBackendsNames = append(filteredBackendsNames, be.Name) continue } @@ -411,6 +403,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { } if broken { + // propagate event to other interested parts, such as cache invalidator for _, l := range cp.listeners { l() } diff --git a/proxyd/proxyd/integration_tests/failover_test.go b/proxyd/proxyd/integration_tests/failover_test.go index 119a901..501542a 100644 --- a/proxyd/proxyd/integration_tests/failover_test.go +++ b/proxyd/proxyd/integration_tests/failover_test.go @@ -190,7 +190,7 @@ func TestOutOfServiceInterval(t *testing.T) { require.NoError(t, err) require.Equal(t, 200, statusCode) RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 2, len(badBackend.Requests())) + require.Equal(t, 4, len(badBackend.Requests())) require.Equal(t, 2, len(goodBackend.Requests())) _, statusCode, err = client.SendBatchRPC( @@ -199,7 +199,7 @@ func TestOutOfServiceInterval(t *testing.T) { ) require.NoError(t, err) require.Equal(t, 200, statusCode) - require.Equal(t, 2, len(badBackend.Requests())) + require.Equal(t, 8, len(badBackend.Requests())) require.Equal(t, 4, len(goodBackend.Requests())) time.Sleep(time.Second) @@ -209,7 +209,7 @@ func TestOutOfServiceInterval(t *testing.T) { require.NoError(t, err) require.Equal(t, 200, statusCode) RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 3, len(badBackend.Requests())) + require.Equal(t, 9, len(badBackend.Requests())) require.Equal(t, 4, len(goodBackend.Requests())) } @@ -261,7 +261,6 @@ func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { config.BackendOptions.MaxRetries = 2 // Setup redis to detect offline backends config.Redis.URL = fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()) - redisClient, err := proxyd.NewRedisClient(config.Redis.URL) require.NoError(t, err) goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse, goodResponse)) @@ -286,10 +285,4 @@ func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { RequireEqualJSON(t, []byte(asArray(goodResponse, goodResponse)), res) require.Equal(t, 1, len(badBackend.Requests())) require.Equal(t, 1, len(goodBackend.Requests())) - - rr := proxyd.NewRedisRateLimiter(redisClient) - require.NoError(t, err) - online, err := rr.IsBackendOnline("bad") - require.NoError(t, err) - require.Equal(t, true, online) } diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/proxyd/integration_tests/rate_limit_test.go index dd69089..4e17f62 100644 --- a/proxyd/proxyd/integration_tests/rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/rate_limit_test.go @@ -21,23 +21,6 @@ const frontendOverLimitResponseWithID = `{"error":{"code":-32016,"message":"over var ethChainID = "eth_chainId" -func TestBackendMaxRPSLimit(t *testing.T) { - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) - defer goodBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - config := ReadConfig("backend_rate_limit") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - limitedRes, codes := spamReqs(t, client, ethChainID, 503, 3) - require.Equal(t, 2, codes[200]) - require.Equal(t, 1, codes[503]) - RequireEqualJSON(t, []byte(noBackendsResponse), limitedRes) -} - func TestFrontendMaxRPSLimit(t *testing.T) { goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) defer goodBackend.Close() diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go index ed33779..c0907fe 100644 --- a/proxyd/proxyd/integration_tests/ws_test.go +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -270,32 +270,3 @@ func TestWSClientClosure(t *testing.T) { }) } } - -func TestWSClientMaxConns(t *testing.T) { - backend := NewMockWSBackend(nil, nil, nil) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - - config := ReadConfig("ws") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - doneCh := make(chan struct{}, 1) - _, err = NewProxydWSClient("ws://127.0.0.1:8546", nil, nil) - require.NoError(t, err) - _, err = NewProxydWSClient("ws://127.0.0.1:8546", nil, func(err error) { - require.Contains(t, err.Error(), "unexpected EOF") - doneCh <- struct{}{} - }) - require.NoError(t, err) - - timeout := time.NewTicker(30 * time.Second) - select { - case <-timeout.C: - t.Fatalf("timed out") - case <-doneCh: - return - } -} diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 4fc286e..20faa67 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -51,19 +51,6 @@ func Start(config *Config) (*Server, func(), error) { return nil, nil, errors.New("must specify a Redis URL if UseRedis is true in rate limit config") } - var lim BackendRateLimiter - var err error - if config.RateLimit.EnableBackendRateLimiter { - if redisClient != nil { - lim = NewRedisRateLimiter(redisClient) - } else { - log.Warn("redis is not configured, using local rate limiter") - lim = NewLocalBackendRateLimiter() - } - } else { - lim = noopBackendRateLimiter - } - // While modifying shared globals is a bad practice, the alternative // is to clone these errors on every invocation. This is inefficient. // We'd also have to make sure that errors.Is and errors.As continue @@ -159,10 +146,14 @@ func Start(config *Config) (*Server, func(), error) { opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP"))) opts = append(opts, WithSkipPeerCountCheck(cfg.SkipPeerCountCheck)) - back := NewBackend(name, rpcURL, wsURL, lim, rpcRequestSemaphore, opts...) + back := NewBackend(name, rpcURL, wsURL, rpcRequestSemaphore, opts...) backendNames = append(backendNames, name) backendsByName[name] = back - log.Info("configured backend", "name", name, "rpc_url", rpcURL, "ws_url", wsURL) + log.Info("configured backend", + "name", name, + "backend_names", backendNames, + "rpc_url", rpcURL, + "ws_url", wsURL) } backendGroups := make(map[string]*BackendGroup) @@ -352,9 +343,6 @@ func Start(config *Config) (*Server, func(), error) { gasPriceLVC.Stop() } srv.Shutdown() - if err := lim.FlushBackendWSConns(backendNames); err != nil { - log.Error("error flushing backend ws conns", "err", err) - } log.Info("goodbye") } diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index b52224c..794d4f8 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -2,6 +2,8 @@ package proxyd import ( "context" + "crypto/rand" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -591,6 +593,14 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context ) } +func randStr(l int) string { + b := make([]byte, l) + if _, err := rand.Read(b); err != nil { + panic(err) + } + return hex.EncodeToString(b) +} + func (s *Server) isUnlimitedOrigin(origin string) bool { for _, pat := range s.limExemptOrigins { if pat.MatchString(origin) { From 3704119064a498a0a734bcb744a2177aa1bb8d5b Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 15 May 2023 12:28:41 -0700 Subject: [PATCH 094/212] configs --- proxyd/proxyd/config.go | 15 +++++++-------- .../testdata/backend_rate_limit.toml | 3 --- .../testdata/out_of_service_interval.toml | 3 --- proxyd/proxyd/integration_tests/testdata/ws.toml | 3 --- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 4f36931..2edd5f9 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -43,14 +43,13 @@ type MetricsConfig struct { } type RateLimitConfig struct { - UseRedis bool `toml:"use_redis"` - EnableBackendRateLimiter bool `toml:"enable_backend_rate_limiter"` - BaseRate int `toml:"base_rate"` - BaseInterval TOMLDuration `toml:"base_interval"` - ExemptOrigins []string `toml:"exempt_origins"` - ExemptUserAgents []string `toml:"exempt_user_agents"` - ErrorMessage string `toml:"error_message"` - MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` + UseRedis bool `toml:"use_redis"` + BaseRate int `toml:"base_rate"` + BaseInterval TOMLDuration `toml:"base_interval"` + ExemptOrigins []string `toml:"exempt_origins"` + ExemptUserAgents []string `toml:"exempt_user_agents"` + ErrorMessage string `toml:"error_message"` + MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` } type RateLimitMethodOverride struct { diff --git a/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml index 17500f3..e846c27 100644 --- a/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml @@ -16,6 +16,3 @@ backends = ["good"] [rpc_method_mappings] eth_chainId = "main" - -[rate_limit] -enable_backend_rate_limiter = true \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml b/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml index 4611251..157fa06 100644 --- a/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml +++ b/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml @@ -20,6 +20,3 @@ backends = ["bad", "good"] [rpc_method_mappings] eth_chainId = "main" - -[rate_limit] -enable_backend_rate_limiter = true \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/testdata/ws.toml b/proxyd/proxyd/integration_tests/testdata/ws.toml index 7340717..4642e6b 100644 --- a/proxyd/proxyd/integration_tests/testdata/ws.toml +++ b/proxyd/proxyd/integration_tests/testdata/ws.toml @@ -26,6 +26,3 @@ backends = ["good"] [rpc_method_mappings] eth_chainId = "main" - -[rate_limit] -enable_backend_rate_limiter = true \ No newline at end of file From 36e8e7a473831e422c658a4627ea735a55619e15 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Mon, 15 May 2023 22:49:47 -0700 Subject: [PATCH 095/212] remove unused integration_tests/testdata/backend_rate_limit.toml --- .../testdata/backend_rate_limit.toml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml diff --git a/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml deleted file mode 100644 index e846c27..0000000 --- a/proxyd/proxyd/integration_tests/testdata/backend_rate_limit.toml +++ /dev/null @@ -1,18 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" -max_rps = 2 - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" From 4c086810437295e5cf7d5cdbd307e094d78a1a7f Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 17 May 2023 21:56:55 -0700 Subject: [PATCH 096/212] move consensus shutdown nested inside the backend group --- proxyd/proxyd/backend.go | 6 ++++++ proxyd/proxyd/server.go | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 4e8fd70..fea2138 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -706,6 +706,12 @@ func (b *BackendGroup) loadBalancedConsensusGroup() []*Backend { return backendsHealthy } +func (bg *BackendGroup) Shutdown() { + if bg.Consensus != nil { + bg.Consensus.Shutdown() + } +} + func calcBackoff(i int) time.Duration { jitter := float64(rand.Int63n(250)) ms := math.Min(math.Pow(2, float64(i))*1000+jitter, 3000) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 794d4f8..d813bed 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -225,9 +225,7 @@ func (s *Server) Shutdown() { _ = s.wsServer.Shutdown(context.Background()) } for _, bg := range s.BackendGroups { - if bg.Consensus != nil { - bg.Consensus.Shutdown() - } + bg.Shutdown() } } From d4956ee655ec801fc7513b3d4018d9f64af13270 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 17 May 2023 21:57:42 -0700 Subject: [PATCH 097/212] rename backend group receivers to be bg instead of just b to avoid misreading as a backend --- proxyd/proxyd/backend.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index fea2138..fdbea53 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -531,23 +531,23 @@ type BackendGroup struct { Consensus *ConsensusPoller } -func (b *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { +func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { if len(rpcReqs) == 0 { return nil, nil } - backends := b.Backends + backends := bg.Backends overriddenResponses := make([]*indexedReqRes, 0) rewrittenReqs := make([]*RPCReq, 0, len(rpcReqs)) - if b.Consensus != nil { + if bg.Consensus != nil { // When `consensus_aware` is set to `true`, the backend group acts as a load balancer // serving traffic from any backend that agrees in the consensus group - backends = b.loadBalancedConsensusGroup() + backends = bg.loadBalancedConsensusGroup() // We also rewrite block tags to enforce compliance with consensus - rctx := RewriteContext{latest: b.Consensus.GetConsensusBlockNumber()} + rctx := RewriteContext{latest: bg.Consensus.GetConsensusBlockNumber()} for i, req := range rpcReqs { res := RPCRes{JSONRPC: JSONRPCVersion, ID: req.ID} @@ -635,8 +635,8 @@ func (b *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch b return nil, ErrNoBackends } -func (b *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { - for _, back := range b.Backends { +func (bg *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { + for _, back := range bg.Backends { proxier, err := back.ProxyWS(clientConn, methodWhitelist) if errors.Is(err, ErrBackendOffline) { log.Warn( @@ -672,8 +672,8 @@ func (b *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, return nil, ErrNoBackends } -func (b *BackendGroup) loadBalancedConsensusGroup() []*Backend { - cg := b.Consensus.GetConsensusGroup() +func (bg *BackendGroup) loadBalancedConsensusGroup() []*Backend { + cg := bg.Consensus.GetConsensusGroup() backendsHealthy := make([]*Backend, 0, len(cg)) backendsDegraded := make([]*Backend, 0, len(cg)) From 7950eb9d7dc6fba7eed1c9970373e772f59bb43c Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 18 May 2023 15:41:01 -0700 Subject: [PATCH 098/212] fix(proxyd): clean up cache initialization --- proxyd/proxyd/config.go | 4 +--- proxyd/proxyd/proxyd.go | 16 ---------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 2edd5f9..0e75769 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -26,9 +26,7 @@ type ServerConfig struct { } type CacheConfig struct { - Enabled bool `toml:"enabled"` - BlockSyncRPCURL string `toml:"block_sync_rpc_url"` - NumBlockConfirmations int `toml:"num_block_confirmations"` + Enabled bool `toml:"enabled"` } type RedisConfig struct { diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index b145b15..afb27bc 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -9,7 +9,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/go-redis/redis/v8" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -206,27 +205,12 @@ func Start(config *Config) (*Server, func(), error) { rpcCache RPCCache ) if config.Cache.Enabled { - if config.Cache.BlockSyncRPCURL == "" { - return nil, nil, fmt.Errorf("block sync node required for caching") - } - blockSyncRPCURL, err := ReadFromEnvOrConfig(config.Cache.BlockSyncRPCURL) - if err != nil { - return nil, nil, err - } - if redisClient == nil { log.Warn("redis is not configured, using in-memory cache") cache = newMemoryCache() } else { cache = newRedisCache(redisClient, config.Redis.Namespace) } - // Ideally, the BlocKSyncRPCURL should be the sequencer or a HA replica that's not far behind - ethClient, err := ethclient.Dial(blockSyncRPCURL) - if err != nil { - return nil, nil, err - } - defer ethClient.Close() - rpcCache = newRPCCache(newCacheWithCompression(cache)) } From 5616f40a162432aeca01d6b301d67f745709776f Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 18 May 2023 16:30:33 -0700 Subject: [PATCH 099/212] also remove from config files --- proxyd/proxyd/integration_tests/testdata/caching.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/proxyd/proxyd/integration_tests/testdata/caching.toml b/proxyd/proxyd/integration_tests/testdata/caching.toml index 246a16e..f3c1327 100644 --- a/proxyd/proxyd/integration_tests/testdata/caching.toml +++ b/proxyd/proxyd/integration_tests/testdata/caching.toml @@ -10,8 +10,6 @@ namespace = "proxyd" [cache] enabled = true -block_sync_rpc_url = "$GOOD_BACKEND_RPC_URL" - [backends] [backends.good] From 10b8b8e32af76dee6f4061e959f1f3b5748b683e Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 19 May 2023 16:13:03 -0700 Subject: [PATCH 100/212] fix(proxyd): avoid caching tx hash based methods, since the response may change with a reorg --- proxyd/proxyd/cache.go | 2 - proxyd/proxyd/cache_test.go | 42 ------------------- .../proxyd/integration_tests/caching_test.go | 24 +++++------ 3 files changed, 12 insertions(+), 56 deletions(-) diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 4f978c5..9684b75 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -130,10 +130,8 @@ func newRPCCache(cache Cache) RPCCache { "eth_getBlockTransactionCountByHash": staticHandler, "eth_getUncleCountByBlockHash": staticHandler, "eth_getBlockByHash": staticHandler, - "eth_getTransactionByHash": staticHandler, "eth_getTransactionByBlockHashAndIndex": staticHandler, "eth_getUncleByBlockHashAndIndex": staticHandler, - "eth_getTransactionReceipt": staticHandler, } return &rpcCache{ cache: cache, diff --git a/proxyd/proxyd/cache_test.go b/proxyd/proxyd/cache_test.go index 0deb0e3..727cdc7 100644 --- a/proxyd/proxyd/cache_test.go +++ b/proxyd/proxyd/cache_test.go @@ -87,34 +87,6 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { }, name: "eth_getBlockByHash", }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getTransactionByHash", - Params: mustMarshalJSON([]string{"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"}), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `{"eth_getTransactionByHash":"!"}`, - ID: ID, - }, - name: "eth_getTransactionByHash", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getTransactionByBlockHashAndIndex", - Params: mustMarshalJSON([]string{"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", "0x55"}), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `{"eth_getTransactionByBlockHashAndIndex":"!"}`, - ID: ID, - }, - name: "eth_getTransactionByBlockHashAndIndex", - }, { req: &RPCReq{ JSONRPC: "2.0", @@ -129,20 +101,6 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { }, name: "eth_getUncleByBlockHashAndIndex", }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getTransactionReceipt", - Params: mustMarshalJSON([]string{"0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5"}), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `{"eth_getTransactionReceipt":"!"}`, - ID: ID, - }, - name: "eth_getTransactionReceipt", - }, } for _, rpc := range rpcs { diff --git a/proxyd/proxyd/integration_tests/caching_test.go b/proxyd/proxyd/integration_tests/caching_test.go index a69ec4f..e4a64d9 100644 --- a/proxyd/proxyd/integration_tests/caching_test.go +++ b/proxyd/proxyd/integration_tests/caching_test.go @@ -77,12 +77,6 @@ func TestCaching(t *testing.T) { "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByHash\", \"id\": 999}", 1, }, - { - "eth_getTransactionByHash", - []interface{}{"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"}, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionByHash\", \"id\": 999}", - 1, - }, { "eth_getTransactionByBlockHashAndIndex", []interface{}{"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", "0x55"}, @@ -95,12 +89,6 @@ func TestCaching(t *testing.T) { "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getUncleByBlockHashAndIndex\", \"id\": 999}", 1, }, - { - "eth_getTransactionReceipt", - []interface{}{"0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5"}, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionReceipt\", \"id\": 999}", - 1, - }, /* not cacheable */ { "eth_getBlockByNumber", @@ -111,6 +99,18 @@ func TestCaching(t *testing.T) { "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByNumber\", \"id\": 999}", 2, }, + { + "eth_getTransactionReceipt", + []interface{}{"0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionReceipt\", \"id\": 999}", + 2, + }, + { + "eth_getTransactionByHash", + []interface{}{"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionByHash\", \"id\": 999}", + 2, + }, { "eth_call", []interface{}{ From 1d9406daa6b7feaacb48601bc21079c43236b178 Mon Sep 17 00:00:00 2001 From: felipe andrade <130432649+felipe-op@users.noreply.github.com> Date: Thu, 25 May 2023 09:16:16 -0700 Subject: [PATCH 101/212] record sliding window metrics (#5782) --- proxyd/proxyd/backend.go | 9 +++++++ proxyd/proxyd/metrics.go | 56 +++++++++++++++++++++++++++++++++------- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index fdbea53..b57c938 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -374,6 +374,7 @@ func (b *Backend) ForwardRPC(ctx context.Context, res *RPCRes, id string, method func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { // we are concerned about network error rates, so we record 1 request independently of how many are in the batch b.networkRequestsSlidingWindow.Incr() + RecordBackendNetworkRequestCountSlidingWindow(b, b.networkRequestsSlidingWindow.Count()) isSingleElementBatch := len(rpcReqs) == 1 @@ -390,6 +391,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool httpReq, err := http.NewRequestWithContext(ctx, "POST", b.rpcURL, bytes.NewReader(body)) if err != nil { b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) return nil, wrapErr(err, "error creating backend request") } @@ -411,6 +413,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool httpRes, err := b.client.DoLimited(httpReq) if err != nil { b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) return nil, wrapErr(err, "error in backend request") } @@ -429,6 +432,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // Alchemy returns a 400 on bad JSONs, so handle that case if httpRes.StatusCode != 200 && httpRes.StatusCode != 400 { b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) return nil, fmt.Errorf("response code %d", httpRes.StatusCode) } @@ -436,6 +440,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool resB, err := io.ReadAll(io.LimitReader(httpRes.Body, b.maxResponseSize)) if err != nil { b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) return nil, wrapErr(err, "error reading response body") } @@ -453,15 +458,18 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // Infura may return a single JSON-RPC response if, for example, the batch contains a request for an unsupported method if responseIsNotBatched(resB) { b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) return nil, ErrBackendUnexpectedJSONRPC } b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) return nil, ErrBackendBadResponse } } if len(rpcReqs) != len(res) { b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) return nil, ErrBackendUnexpectedJSONRPC } @@ -474,6 +482,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool } duration := time.Since(start) b.latencySlidingWindow.Add(float64(duration)) + RecordBackendNetworkLatencyAverageSlidingWindow(b, b.latencySlidingWindow.Avg()) sortBatchRPCResponse(rpcReqs, res) return res, nil diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 3420b71..3f462cf 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -309,6 +309,30 @@ var ( }, []string{ "backend_name", }) + + avgLatencyBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_avg_latency", + Help: "Average latency per backend", + }, []string{ + "backend_name", + }) + + networkErrorCountBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_net_error_count", + Help: "Network error count per backend", + }, []string{ + "backend_name", + }) + + requestCountBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_request_count", + Help: "Request count per backend", + }, []string{ + "backend_name", + }) ) func RecordRedisError(source string) { @@ -390,30 +414,42 @@ func RecordGroupTotalCount(group *BackendGroup, count int) { consensusGroupTotalCount.WithLabelValues(group.Name).Set(float64(count)) } -func RecordBackendLatestBlock(be *Backend, blockNumber hexutil.Uint64) { - backendLatestBlockBackend.WithLabelValues(be.Name).Set(float64(blockNumber)) +func RecordBackendLatestBlock(b *Backend, blockNumber hexutil.Uint64) { + backendLatestBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) } -func RecordConsensusBackendBanned(be *Backend, banned bool) { +func RecordConsensusBackendBanned(b *Backend, banned bool) { v := float64(0) if banned { v = float64(1) } - consensusBannedBackends.WithLabelValues(be.Name).Set(v) + consensusBannedBackends.WithLabelValues(b.Name).Set(v) } -func RecordConsensusBackendPeerCount(be *Backend, peerCount uint64) { - consensusPeerCountBackend.WithLabelValues(be.Name).Set(float64(peerCount)) +func RecordConsensusBackendPeerCount(b *Backend, peerCount uint64) { + consensusPeerCountBackend.WithLabelValues(b.Name).Set(float64(peerCount)) } -func RecordConsensusBackendInSync(be *Backend, inSync bool) { +func RecordConsensusBackendInSync(b *Backend, inSync bool) { v := float64(0) if inSync { v = float64(1) } - consensusInSyncBackend.WithLabelValues(be.Name).Set(v) + consensusInSyncBackend.WithLabelValues(b.Name).Set(v) } -func RecordConsensusBackendUpdateDelay(be *Backend, delay time.Duration) { - consensusUpdateDelayBackend.WithLabelValues(be.Name).Set(float64(delay.Milliseconds())) +func RecordConsensusBackendUpdateDelay(b *Backend, delay time.Duration) { + consensusUpdateDelayBackend.WithLabelValues(b.Name).Set(float64(delay.Milliseconds())) +} + +func RecordBackendNetworkLatencyAverageSlidingWindow(b *Backend, avgLatency float64) { + avgLatencyBackend.WithLabelValues(b.Name).Set(avgLatency) +} + +func RecordBackendNetworkRequestCountSlidingWindow(b *Backend, count uint) { + requestCountBackend.WithLabelValues(b.Name).Set(float64(count)) +} + +func RecordBackendNetworkErrorCountSlidingWindow(b *Backend, count uint) { + networkErrorCountBackend.WithLabelValues(b.Name).Set(float64(count)) } From 56938b6b0127bd889523e9d3b6a062859ed0f65d Mon Sep 17 00:00:00 2001 From: felipe andrade <130432649+felipe-op@users.noreply.github.com> Date: Thu, 25 May 2023 11:04:14 -0700 Subject: [PATCH 102/212] normalize latency in ms (#5783) --- proxyd/proxyd/backend.go | 2 +- proxyd/proxyd/metrics.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index b57c938..8e8db75 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -482,7 +482,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool } duration := time.Since(start) b.latencySlidingWindow.Add(float64(duration)) - RecordBackendNetworkLatencyAverageSlidingWindow(b, b.latencySlidingWindow.Avg()) + RecordBackendNetworkLatencyAverageSlidingWindow(b, time.Duration(b.latencySlidingWindow.Avg())) sortBatchRPCResponse(rpcReqs, res) return res, nil diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 3f462cf..884e783 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -442,8 +442,8 @@ func RecordConsensusBackendUpdateDelay(b *Backend, delay time.Duration) { consensusUpdateDelayBackend.WithLabelValues(b.Name).Set(float64(delay.Milliseconds())) } -func RecordBackendNetworkLatencyAverageSlidingWindow(b *Backend, avgLatency float64) { - avgLatencyBackend.WithLabelValues(b.Name).Set(avgLatency) +func RecordBackendNetworkLatencyAverageSlidingWindow(b *Backend, avgLatency time.Duration) { + avgLatencyBackend.WithLabelValues(b.Name).Set(float64(avgLatency.Milliseconds())) } func RecordBackendNetworkRequestCountSlidingWindow(b *Backend, count uint) { From 61db3000424d3a3555114bced4eccde9a2537b4f Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Thu, 25 May 2023 13:01:34 -0600 Subject: [PATCH 103/212] proxyd: Pass through finalized and safe tags --- proxyd/proxyd/rewriter.go | 8 ++++++++ proxyd/proxyd/rewriter_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index 35d78df..ab13a05 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -159,6 +159,13 @@ func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (b } func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { + // If a tag is the safe or finalized block number, don't rewrite it. + // We have a custom check here because the rpc.BlockNumberOrHash type + // doesn't support these custom tags yet. + if current == "safe" || current == "finalized" { + return current, false, nil + } + jv, err := json.Marshal(current) if err != nil { return "", false, err @@ -177,5 +184,6 @@ func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { return "", false, ErrRewriteBlockOutOfRange } } + return current, false, nil } diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go index ed5e6a7..5b23c84 100644 --- a/proxyd/proxyd/rewriter_test.go +++ b/proxyd/proxyd/rewriter_test.go @@ -308,6 +308,38 @@ func TestRewriteRequest(t *testing.T) { require.Equal(t, hexutil.Uint64(100).String(), p[0]) }, }, + { + name: "eth_getBlockByNumber finalized", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"finalized"})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, "finalized", p[0]) + }, + }, + { + name: "eth_getBlockByNumber safe", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"safe"})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, "safe", p[0]) + }, + }, { name: "eth_getBlockByNumber within range", args: args{ From d5a476c5e812e740d09f3c6267fbae8326e1f39e Mon Sep 17 00:00:00 2001 From: felipe andrade <130432649+felipe-op@users.noreply.github.com> Date: Thu, 25 May 2023 22:02:19 -0700 Subject: [PATCH 104/212] fix(proxyd): use go-ethereum 1.12.0 parser for rpc.BlockNumberOrHash (#5791) * bump go-ethereum to 1.12.0 to support finalized and safe block tags * fix index out of range error for malformed requests * goimports --- proxyd/proxyd/go.mod | 69 +-- proxyd/proxyd/go.sum | 791 +++++++++------------------------ proxyd/proxyd/rewriter.go | 30 +- proxyd/proxyd/rewriter_test.go | 15 + proxyd/proxyd/server.go | 9 +- 5 files changed, 286 insertions(+), 628 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 97c7b02..d7b1c89 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -3,67 +3,70 @@ module github.com/ethereum-optimism/optimism/proxyd go 1.18 require ( - github.com/BurntSushi/toml v0.4.1 + github.com/BurntSushi/toml v1.2.0 github.com/alicebob/miniredis v2.5.0+incompatible - github.com/ethereum/go-ethereum v1.10.17 + github.com/emirpasic/gods v1.18.1 + github.com/ethereum/go-ethereum v1.12.0 github.com/go-redis/redis/v8 v8.11.4 - github.com/golang/snappy v0.0.4 + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.11.1 + github.com/prometheus/client_golang v1.14.0 github.com/rs/cors v1.8.2 - github.com/stretchr/testify v1.7.0 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - gopkg.in/yaml.v2 v2.4.0 + github.com/stretchr/testify v1.8.1 + golang.org/x/sync v0.1.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/DataDog/zstd v1.5.2 // indirect github.com/VictoriaMetrics/fastcache v1.9.0 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd v0.22.0-beta // indirect - github.com/btcsuite/btcd/btcec/v2 v2.1.2 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // 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.9.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect + github.com/cockroachdb/redact v1.1.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deckarep/golang-set v1.8.0 // indirect + github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/edsrzf/mmap-go v1.1.0 // indirect - github.com/emirpasic/gods v1.18.1 // indirect - github.com/fjl/memsize v0.0.1 // indirect - github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect + github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/gomodule/redigo v1.8.8 // indirect - github.com/google/go-cmp v0.5.8 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/hashicorp/go-bexpr v0.1.11 // indirect - github.com/huin/goupnp v1.0.3 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect + github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect + github.com/klauspost/compress v1.15.15 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.30.0 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - github.com/prometheus/tsdb v0.10.0 // indirect - github.com/rjeczalik/notify v0.9.2 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.39.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect - github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect - google.golang.org/protobuf v1.27.1 // indirect + golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 4c71d15..c9927a4 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -1,88 +1,33 @@ 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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/VictoriaMetrics/fastcache v1.9.0 h1:oMwsS6c8abz98B7ytAewQ7M1ZN/Im/iwKoE1euaFvhs= github.com/VictoriaMetrics/fastcache v1.9.0/go.mod h1:otoTS3xu+6IzF/qByjqzjp3rTuzM3Qf0ScU1UTj97iU= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= -github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= -github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= -github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= -github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= -github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= 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/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= -github.com/btcsuite/btcd/btcec/v2 v2.1.2 h1:YoYoC9J0jwfukodSBMzZYUVQ8PTiYg4BnOWiJVzTmLs= -github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +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/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= @@ -93,123 +38,97 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/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/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= -github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= +github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= +github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +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-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= +github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= +github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= +github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= -github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= -github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +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.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= -github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= -github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 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.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= -github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= -github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= +github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -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/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= +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/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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= @@ -218,176 +137,116 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +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/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/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.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= -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/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= -github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= +github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= -github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= -github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= -github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= -github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= -github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= -github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= -github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= -github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -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/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= +github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= +github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= +github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= +github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= +github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= +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/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= -github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= -github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= @@ -398,168 +257,111 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= 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 v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= -github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= -github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= +github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= -github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d h1:vmirMegf1vqPJ+lDBxLQ0MAt3tz+JL57UPxu44JBOjA= -github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d/go.mod h1:97vT0Rym0wCnK4B++hNA3nCetr0Mh1KXaVxzSt1arjg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -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.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -567,267 +369,110 @@ golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73r 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-20181114220301-adae6a3d119a/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-20181220203305-927f97764cc3/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-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/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-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/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-20190206041539-40960b6deb8e/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +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.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 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= @@ -836,49 +481,35 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= 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/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/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-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index ab13a05..be539bc 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -85,8 +85,12 @@ func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (Rewri return RewriteOverrideError, err } - if len(p) <= pos { + // we assume latest if the param is missing, + // and we don't rewrite if there is not enough params + if len(p) == pos { p = append(p, "latest") + } else if len(p) < pos { + return RewriteNone, nil } val, rw, err := rewriteTag(rctx, p[pos].(string)) @@ -159,13 +163,6 @@ func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (b } func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { - // If a tag is the safe or finalized block number, don't rewrite it. - // We have a custom check here because the rpc.BlockNumberOrHash type - // doesn't support these custom tags yet. - if current == "safe" || current == "finalized" { - return current, false, nil - } - jv, err := json.Marshal(current) if err != nil { return "", false, err @@ -177,10 +174,21 @@ func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { return "", false, err } - if bnh.BlockNumber != nil && *bnh.BlockNumber == rpc.LatestBlockNumber { + // this is a hash, not a block + if bnh.BlockNumber == nil { + return current, false, nil + } + + switch *bnh.BlockNumber { + case rpc.SafeBlockNumber, + rpc.FinalizedBlockNumber, + rpc.PendingBlockNumber, + rpc.EarliestBlockNumber: + return current, false, nil + case rpc.LatestBlockNumber: return rctx.latest.String(), true, nil - } else if bnh.BlockNumber != nil { - if hexutil.Uint64(bnh.BlockNumber.Int64()) > rctx.latest { + default: + if bnh.BlockNumber.Int64() > int64(rctx.latest) { return "", false, ErrRewriteBlockOutOfRange } } diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go index 5b23c84..ae5c9c8 100644 --- a/proxyd/proxyd/rewriter_test.go +++ b/proxyd/proxyd/rewriter_test.go @@ -166,6 +166,21 @@ func TestRewriteRequest(t *testing.T) { require.Equal(t, hexutil.Uint64(100).String(), p[1]) }, }, + { + name: "eth_getCode not enough params, should do nothing", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 0, len(p)) + }, + }, { name: "eth_getCode latest", args: args{ diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index d813bed..3ca10af 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -16,6 +16,8 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -651,19 +653,18 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { // Convert the transaction into a Message object so that we can get the // sender. This method performs an ecrecover, which can be expensive. - msg, err := tx.AsMessage(types.LatestSignerForChainID(tx.ChainId()), nil) + msg, err := core.TransactionToMessage(tx, types.LatestSignerForChainID(tx.ChainId()), nil) if err != nil { log.Debug("could not get message from transaction", "err", err, "req_id", GetReqID(ctx)) return ErrInvalidParams(err.Error()) } - - ok, err := s.senderLim.Take(ctx, fmt.Sprintf("%s:%d", msg.From().Hex(), tx.Nonce())) + ok, err := s.senderLim.Take(ctx, fmt.Sprintf("%s:%d", msg.From.Hex(), tx.Nonce())) if err != nil { log.Error("error taking from sender limiter", "err", err, "req_id", GetReqID(ctx)) return ErrInternal } if !ok { - log.Debug("sender rate limit exceeded", "sender", msg.From(), "req_id", GetReqID(ctx)) + log.Debug("sender rate limit exceeded", "sender", msg.From.Hex(), "req_id", GetReqID(ctx)) return ErrOverSenderRateLimit } From af863d39de25d252479ed6e7266dc7c893c1c843 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 25 May 2023 21:37:47 -0700 Subject: [PATCH 105/212] consensus for {safe,finalized} and rewrite tags --- proxyd/proxyd/backend.go | 6 +- proxyd/proxyd/consensus_poller.go | 117 +++++++--- proxyd/proxyd/consensus_tracker.go | 78 +++++-- .../integration_tests/consensus_test.go | 207 ++++++++++++++++-- .../testdata/consensus_responses.yml | 66 ++++++ proxyd/proxyd/rewriter.go | 12 +- proxyd/proxyd/rewriter_test.go | 12 +- .../tools/mockserver/handler/handler.go | 2 +- 8 files changed, 423 insertions(+), 77 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 8e8db75..3946995 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -556,7 +556,11 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch backends = bg.loadBalancedConsensusGroup() // We also rewrite block tags to enforce compliance with consensus - rctx := RewriteContext{latest: bg.Consensus.GetConsensusBlockNumber()} + rctx := RewriteContext{ + latest: bg.Consensus.GetLatestBlockNumber(), + finalized: bg.Consensus.GetFinalizedBlockNumber(), + safe: bg.Consensus.GetSafeBlockNumber(), + } for i, req := range rpcReqs { res := RPCRes{JSONRPC: JSONRPCVersion, ID: req.ID} diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index a170aca..5c5edcd 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -46,8 +46,12 @@ type backendState struct { latestBlockNumber hexutil.Uint64 latestBlockHash string - peerCount uint64 - inSync bool + + finalizedBlockNumber hexutil.Uint64 + safeBlockNumber hexutil.Uint64 + + peerCount uint64 + inSync bool lastUpdate time.Time @@ -65,9 +69,19 @@ func (cp *ConsensusPoller) GetConsensusGroup() []*Backend { return g } -// GetConsensusBlockNumber returns the agreed block number in a consensus -func (ct *ConsensusPoller) GetConsensusBlockNumber() hexutil.Uint64 { - return ct.tracker.GetConsensusBlockNumber() +// GetLatestBlockNumber returns the `latest` agreed block number in a consensus +func (ct *ConsensusPoller) GetLatestBlockNumber() hexutil.Uint64 { + return ct.tracker.GetLatestBlockNumber() +} + +// GetFinalizedBlockNumber returns the `finalized` agreed block number in a consensus +func (ct *ConsensusPoller) GetFinalizedBlockNumber() hexutil.Uint64 { + return ct.tracker.GetFinalizedBlockNumber() +} + +// GetSafeBlockNumber returns the `safe` agreed block number in a consensus +func (ct *ConsensusPoller) GetSafeBlockNumber() hexutil.Uint64 { + return ct.tracker.GetSafeBlockNumber() } func (cp *ConsensusPoller) Shutdown() { @@ -261,7 +275,19 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { log.Warn("error updating backend", "name", be.Name, "err", err) } - changed, updateDelay := cp.setBackendState(be, peerCount, inSync, latestBlockNumber, latestBlockHash) + finalizedBlockNumber, _, err := cp.fetchBlock(ctx, be, "finalized") + if err != nil { + log.Warn("error updating backend", "name", be.Name, "err", err) + } + + safeBlockNumber, _, err := cp.fetchBlock(ctx, be, "safe") + if err != nil { + log.Warn("error updating backend", "name", be.Name, "err", err) + } + + changed, updateDelay := cp.setBackendState(be, peerCount, inSync, + latestBlockNumber, latestBlockHash, + finalizedBlockNumber, safeBlockNumber) if changed { RecordBackendLatestBlock(be, latestBlockNumber) @@ -272,21 +298,27 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { "inSync", inSync, "latestBlockNumber", latestBlockNumber, "latestBlockHash", latestBlockHash, + "finalizedBlockNumber", finalizedBlockNumber, + "safeBlockNumber", safeBlockNumber, "updateDelay", updateDelay) } } // UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - var highestBlock hexutil.Uint64 - var lowestBlock hexutil.Uint64 - var lowestBlockHash string + var highestLatestBlock hexutil.Uint64 - currentConsensusBlockNumber := cp.GetConsensusBlockNumber() + var lowestLatestBlock hexutil.Uint64 + var lowestLatestBlockHash string + + var lowestFinalizedBlock hexutil.Uint64 + var lowestSafeBlock hexutil.Uint64 + + currentConsensusBlockNumber := cp.GetLatestBlockNumber() // find the highest block, in order to use it defining the highest non-lagging ancestor block for _, be := range cp.backendGroup.Backends { - peerCount, inSync, backendLatestBlockNumber, _, lastUpdate, _ := cp.getBackendState(be) + peerCount, inSync, backendLatestBlockNumber, _, _, _, lastUpdate, _ := cp.getBackendState(be) if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { continue @@ -298,14 +330,14 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { continue } - if backendLatestBlockNumber > highestBlock { - highestBlock = backendLatestBlockNumber + if backendLatestBlockNumber > highestLatestBlock { + highestLatestBlock = backendLatestBlockNumber } } // find the highest common ancestor block for _, be := range cp.backendGroup.Backends { - peerCount, inSync, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate, _ := cp.getBackendState(be) + peerCount, inSync, backendLatestBlockNumber, backendLatestBlockHash, backendFinalizedBlockNumber, backendSafeBlockNumber, lastUpdate, _ := cp.getBackendState(be) if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { continue @@ -318,23 +350,31 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { } // check if backend is lagging behind the highest block - if backendLatestBlockNumber < highestBlock && uint64(highestBlock-backendLatestBlockNumber) > cp.maxBlockLag { + if backendLatestBlockNumber < highestLatestBlock && uint64(highestLatestBlock-backendLatestBlockNumber) > cp.maxBlockLag { continue } - if lowestBlock == 0 || backendLatestBlockNumber < lowestBlock { - lowestBlock = backendLatestBlockNumber - lowestBlockHash = backendLatestBlockHash + if lowestLatestBlock == 0 || backendLatestBlockNumber < lowestLatestBlock { + lowestLatestBlock = backendLatestBlockNumber + lowestLatestBlockHash = backendLatestBlockHash + } + + if lowestFinalizedBlock == 0 || backendFinalizedBlockNumber < lowestFinalizedBlock { + lowestFinalizedBlock = backendFinalizedBlockNumber + } + + if lowestSafeBlock == 0 || backendSafeBlockNumber < lowestSafeBlock { + lowestSafeBlock = backendSafeBlockNumber } } // no block to propose (i.e. initializing consensus) - if lowestBlock == 0 { + if lowestLatestBlock == 0 { return } - proposedBlock := lowestBlock - proposedBlockHash := lowestBlockHash + proposedBlock := lowestLatestBlock + proposedBlockHash := lowestLatestBlockHash hasConsensus := false // check if everybody agrees on the same block hash @@ -342,8 +382,8 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { consensusBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) filteredBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) - if lowestBlock > currentConsensusBlockNumber { - log.Debug("validating consensus on block", "lowestBlock", lowestBlock) + if lowestLatestBlock > currentConsensusBlockNumber { + log.Debug("validating consensus on block", "lowestLatestBlock", lowestLatestBlock) } broken := false @@ -362,7 +402,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - in sync */ - peerCount, inSync, latestBlockNumber, _, lastUpdate, bannedUntil := cp.getBackendState(be) + peerCount, inSync, latestBlockNumber, _, _, _, lastUpdate, bannedUntil := cp.getBackendState(be) notUpdated := lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) isBanned := time.Now().Before(bannedUntil) notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount @@ -410,7 +450,9 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { log.Info("consensus broken", "currentConsensusBlockNumber", currentConsensusBlockNumber, "proposedBlock", proposedBlock, "proposedBlockHash", proposedBlockHash) } - cp.tracker.SetConsensusBlockNumber(proposedBlock) + cp.tracker.SetLatestBlockNumber(proposedBlock) + cp.tracker.SetFinalizedBlockNumber(lowestFinalizedBlock) + cp.tracker.SetSafeBlockNumber(lowestSafeBlock) cp.consensusGroupMux.Lock() cp.consensusGroup = consensusBackends cp.consensusGroupMux.Unlock() @@ -512,27 +554,38 @@ func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bo return res, nil } -func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, inSync bool, blockNumber hexutil.Uint64, blockHash string, lastUpdate time.Time, bannedUntil time.Time) { +func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, inSync bool, + latestBlockNumber hexutil.Uint64, latestBlockHash string, + finalizedBlockNumber hexutil.Uint64, + safeBlockNumber hexutil.Uint64, + lastUpdate time.Time, bannedUntil time.Time) { bs := cp.backendState[be] defer bs.backendStateMux.Unlock() bs.backendStateMux.Lock() peerCount = bs.peerCount inSync = bs.inSync - blockNumber = bs.latestBlockNumber - blockHash = bs.latestBlockHash + latestBlockNumber = bs.latestBlockNumber + latestBlockHash = bs.latestBlockHash + finalizedBlockNumber = bs.finalizedBlockNumber + safeBlockNumber = bs.safeBlockNumber lastUpdate = bs.lastUpdate bannedUntil = bs.bannedUntil return } -func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync bool, blockNumber hexutil.Uint64, blockHash string) (changed bool, updateDelay time.Duration) { +func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync bool, + latestBlockNumber hexutil.Uint64, latestBlockHash string, + finalizedBlockNumber hexutil.Uint64, + safeBlockNumber hexutil.Uint64) (changed bool, updateDelay time.Duration) { bs := cp.backendState[be] bs.backendStateMux.Lock() - changed = bs.latestBlockHash != blockHash + changed = bs.latestBlockHash != latestBlockHash bs.peerCount = peerCount bs.inSync = inSync - bs.latestBlockNumber = blockNumber - bs.latestBlockHash = blockHash + bs.latestBlockNumber = latestBlockNumber + bs.latestBlockHash = latestBlockHash + bs.finalizedBlockNumber = finalizedBlockNumber + bs.safeBlockNumber = safeBlockNumber updateDelay = time.Since(bs.lastUpdate) bs.lastUpdate = time.Now() bs.backendStateMux.Unlock() diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go index 3bd10e7..83d2ec7 100644 --- a/proxyd/proxyd/consensus_tracker.go +++ b/proxyd/proxyd/consensus_tracker.go @@ -13,35 +13,68 @@ import ( // ConsensusTracker abstracts how we store and retrieve the current consensus // allowing it to be stored locally in-memory or in a shared Redis cluster type ConsensusTracker interface { - GetConsensusBlockNumber() hexutil.Uint64 - SetConsensusBlockNumber(blockNumber hexutil.Uint64) + GetLatestBlockNumber() hexutil.Uint64 + SetLatestBlockNumber(blockNumber hexutil.Uint64) + GetFinalizedBlockNumber() hexutil.Uint64 + SetFinalizedBlockNumber(blockNumber hexutil.Uint64) + GetSafeBlockNumber() hexutil.Uint64 + SetSafeBlockNumber(blockNumber hexutil.Uint64) } // InMemoryConsensusTracker store and retrieve in memory, async-safe type InMemoryConsensusTracker struct { - consensusBlockNumber hexutil.Uint64 + latestBlockNumber hexutil.Uint64 + finalizedBlockNumber hexutil.Uint64 + safeBlockNumber hexutil.Uint64 mutex sync.Mutex } func NewInMemoryConsensusTracker() ConsensusTracker { return &InMemoryConsensusTracker{ - consensusBlockNumber: 0, - mutex: sync.Mutex{}, + mutex: sync.Mutex{}, } } -func (ct *InMemoryConsensusTracker) GetConsensusBlockNumber() hexutil.Uint64 { +func (ct *InMemoryConsensusTracker) GetLatestBlockNumber() hexutil.Uint64 { defer ct.mutex.Unlock() ct.mutex.Lock() - return ct.consensusBlockNumber + return ct.latestBlockNumber } -func (ct *InMemoryConsensusTracker) SetConsensusBlockNumber(blockNumber hexutil.Uint64) { +func (ct *InMemoryConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uint64) { defer ct.mutex.Unlock() ct.mutex.Lock() - ct.consensusBlockNumber = blockNumber + ct.latestBlockNumber = blockNumber +} + +func (ct *InMemoryConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + return ct.finalizedBlockNumber +} + +func (ct *InMemoryConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + ct.finalizedBlockNumber = blockNumber +} + +func (ct *InMemoryConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + return ct.safeBlockNumber +} + +func (ct *InMemoryConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint64) { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + ct.safeBlockNumber = blockNumber } // RedisConsensusTracker uses a Redis `client` to store and retrieve consensus, async-safe @@ -59,14 +92,29 @@ func NewRedisConsensusTracker(ctx context.Context, r *redis.Client, namespace st } } -func (ct *RedisConsensusTracker) key() string { - return fmt.Sprintf("consensus_latest_block:%s", ct.backendGroup) +func (ct *RedisConsensusTracker) key(tag string) string { + return fmt.Sprintf("consensus:%s:%s", ct.backendGroup, tag) } -func (ct *RedisConsensusTracker) GetConsensusBlockNumber() hexutil.Uint64 { - return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key()).Val())) +func (ct *RedisConsensusTracker) GetLatestBlockNumber() hexutil.Uint64 { + return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key("latest")).Val())) } -func (ct *RedisConsensusTracker) SetConsensusBlockNumber(blockNumber hexutil.Uint64) { - ct.client.Set(ct.ctx, ct.key(), blockNumber, 0) +func (ct *RedisConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uint64) { + ct.client.Set(ct.ctx, ct.key("latest"), blockNumber, 0) +} + +func (ct *RedisConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { + return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key("finalized")).Val())) +} + +func (ct *RedisConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { + ct.client.Set(ct.ctx, ct.key("finalized"), blockNumber, 0) +} +func (ct *RedisConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { + return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key("safe")).Val())) +} + +func (ct *RedisConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint64) { + ct.client.Set(ct.ctx, ct.key("safe"), blockNumber, 0) } diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 9320429..d95903e 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -61,7 +61,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.Unban() // unknown consensus at init - require.Equal(t, "0x0", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x0", bg.Consensus.GetLatestBlockNumber().String()) // first poll for _, be := range bg.Backends { @@ -70,7 +70,9 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // consensus at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0x555", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0x551", bg.Consensus.GetSafeBlockNumber().String()) }) t.Run("prevent using a backend with low peer count", func(t *testing.T) { @@ -108,6 +110,16 @@ func TestConsensus(t *testing.T) { Block: "latest", Response: buildGetBlockResponse("0x1", "hash1"), }) + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "finalized", + Response: buildGetBlockResponse("0x1", "hash1"), + }) + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "safe", + Response: buildGetBlockResponse("0x1", "hash1"), + }) h2.AddOverride(&ms.MethodTemplate{ Method: "eth_getBlockByNumber", @@ -126,7 +138,9 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // since we ignored node1, the consensus should be at 0x100 - require.Equal(t, "0x100", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x100", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0x555", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0x551", bg.Consensus.GetSafeBlockNumber().String()) consensusGroup := bg.Consensus.GetConsensusGroup() @@ -166,7 +180,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // since we ignored node1, the consensus should be at 0x100 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) consensusGroup := bg.Consensus.GetConsensusGroup() @@ -201,7 +215,7 @@ func TestConsensus(t *testing.T) { } bg.Consensus.UpdateBackendGroupConsensus(ctx) - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) consensusGroup := bg.Consensus.GetConsensusGroup() @@ -249,7 +263,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) // advance latest on node2 to 0x2 h2.AddOverride(&ms.MethodTemplate{ @@ -265,7 +279,7 @@ func TestConsensus(t *testing.T) { // consensus should stick to 0x1, since node1 is still lagging there bg.Consensus.UpdateBackendGroupConsensus(ctx) - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) // advance latest on node1 to 0x2 h1.AddOverride(&ms.MethodTemplate{ @@ -281,7 +295,82 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // should stick to 0x2, since now all nodes are at 0x2 - require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x2", bg.Consensus.GetLatestBlockNumber().String()) + }) + + t.Run("should use lowest safe and finalized", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "finalized", + Response: buildGetBlockResponse("0x559", "hash559"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "safe", + Response: buildGetBlockResponse("0x558", "hash558"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, "0x555", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0x551", bg.Consensus.GetSafeBlockNumber().String()) + }) + + t.Run("advance safe and finalized", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "finalized", + Response: buildGetBlockResponse("0x556", "hash556"), + }) + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "safe", + Response: buildGetBlockResponse("0x552", "hash552"), + }) + + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "finalized", + Response: buildGetBlockResponse("0x559", "hash559"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "safe", + Response: buildGetBlockResponse("0x558", "hash558"), + }) + + // poll for group consensus + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, "0x556", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0x552", bg.Consensus.GetSafeBlockNumber().String()) }) t.Run("broken consensus", func(t *testing.T) { @@ -300,7 +389,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) // advance latest on both nodes to 0x2 h1.AddOverride(&ms.MethodTemplate{ @@ -321,7 +410,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // at 0x2 - require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x2", bg.Consensus.GetLatestBlockNumber().String()) // make node2 diverge on hash h2.AddOverride(&ms.MethodTemplate{ @@ -337,7 +426,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // should resolve to 0x1, since 0x2 is out of consensus at the moment - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) require.True(t, listenerCalled) }) @@ -353,7 +442,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) // advance latest on both nodes to 0x2 h1.AddOverride(&ms.MethodTemplate{ @@ -374,7 +463,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // at 0x2 - require.Equal(t, "0x2", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x2", bg.Consensus.GetLatestBlockNumber().String()) // advance latest on both nodes to 0x3 h1.AddOverride(&ms.MethodTemplate{ @@ -395,7 +484,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // at 0x3 - require.Equal(t, "0x3", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x3", bg.Consensus.GetLatestBlockNumber().String()) // make node2 diverge on hash for blocks 0x2 and 0x3 h2.AddOverride(&ms.MethodTemplate{ @@ -416,7 +505,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // should resolve to 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) }) t.Run("fork in advanced block", func(t *testing.T) { @@ -430,7 +519,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) // make nodes 1 and 2 advance in forks h1.AddOverride(&ms.MethodTemplate{ @@ -471,7 +560,7 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) // should resolve to 0x1, the highest common ancestor - require.Equal(t, "0x1", bg.Consensus.GetConsensusBlockNumber().String()) + require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) }) t.Run("load balancing should hit both backends", func(t *testing.T) { @@ -607,7 +696,7 @@ func TestConsensus(t *testing.T) { require.Equal(t, totalRequests, len(node1.Requests())+len(node2.Requests())) }) - t.Run("rewrite request of eth_getBlockByNumber", func(t *testing.T) { + t.Run("rewrite request of eth_getBlockByNumber for latest", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() bg.Consensus.Unban() @@ -643,6 +732,88 @@ func TestConsensus(t *testing.T) { require.Equal(t, "0x2", jsonMap["params"].([]interface{})[0]) }) + t.Run("rewrite request of eth_getBlockByNumber for finalized", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + // establish the consensus and ban node2 for now + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x20", "hash20"), + }) + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "finalized", + Response: buildGetBlockResponse("0x5", "hash5"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "net_peerCount", + Block: "", + Response: buildPeerCountResponse(1), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + + node1.Reset() + + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"finalized"}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(node1.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "0x5", jsonMap["params"].([]interface{})[0]) + }) + + t.Run("rewrite request of eth_getBlockByNumber for safe", func(t *testing.T) { + h1.ResetOverrides() + h2.ResetOverrides() + bg.Consensus.Unban() + + // establish the consensus and ban node2 for now + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "latest", + Response: buildGetBlockResponse("0x20", "hash20"), + }) + h1.AddOverride(&ms.MethodTemplate{ + Method: "eth_getBlockByNumber", + Block: "safe", + Response: buildGetBlockResponse("0x1", "hash1"), + }) + h2.AddOverride(&ms.MethodTemplate{ + Method: "net_peerCount", + Block: "", + Response: buildPeerCountResponse(1), + }) + + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + + node1.Reset() + + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"safe"}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(node1.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "0x1", jsonMap["params"].([]interface{})[0]) + }) + t.Run("rewrite request of eth_getBlockByNumber - out of range", func(t *testing.T) { h1.ResetOverrides() h2.ResetOverrides() diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml index 83579e7..ef5b692 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml +++ b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml @@ -63,3 +63,69 @@ "number": "0x3" } } +- method: eth_getBlockByNumber + block: finalized + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_finalized", + "number": "0x555" + } + } +- method: eth_getBlockByNumber + block: 0x555 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_finalized", + "number": "0x555" + } + } +- method: eth_getBlockByNumber + block: safe + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_safe", + "number": "0x551" + } + } +- method: eth_getBlockByNumber + block: 0x555 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_safe", + "number": "0x551" + } + } +- method: eth_getBlockByNumber + block: 0x5 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash5", + "number": "0x5" + } + } +- method: eth_getBlockByNumber + block: 0x20 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash20", + "number": "0x20" + } + } diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index be539bc..ab098d8 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -9,7 +9,9 @@ import ( ) type RewriteContext struct { - latest hexutil.Uint64 + latest hexutil.Uint64 + finalized hexutil.Uint64 + safe hexutil.Uint64 } type RewriteResult uint8 @@ -180,11 +182,13 @@ func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { } switch *bnh.BlockNumber { - case rpc.SafeBlockNumber, - rpc.FinalizedBlockNumber, - rpc.PendingBlockNumber, + case rpc.PendingBlockNumber, rpc.EarliestBlockNumber: return current, false, nil + case rpc.FinalizedBlockNumber: + return rctx.finalized.String(), true, nil + case rpc.SafeBlockNumber: + return rctx.safe.String(), true, nil case rpc.LatestBlockNumber: return rctx.latest.String(), true, nil default: diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go index ae5c9c8..eb7f18f 100644 --- a/proxyd/proxyd/rewriter_test.go +++ b/proxyd/proxyd/rewriter_test.go @@ -326,33 +326,33 @@ func TestRewriteRequest(t *testing.T) { { name: "eth_getBlockByNumber finalized", args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, + rctx: RewriteContext{latest: hexutil.Uint64(100), finalized: hexutil.Uint64(55)}, req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"finalized"})}, res: nil, }, - expected: RewriteNone, + expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { var p []string err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 1, len(p)) - require.Equal(t, "finalized", p[0]) + require.Equal(t, hexutil.Uint64(55).String(), p[0]) }, }, { name: "eth_getBlockByNumber safe", args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, + rctx: RewriteContext{latest: hexutil.Uint64(100), safe: hexutil.Uint64(50)}, req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"safe"})}, res: nil, }, - expected: RewriteNone, + expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { var p []string err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 1, len(p)) - require.Equal(t, "safe", p[0]) + require.Equal(t, hexutil.Uint64(50).String(), p[0]) }, }, { diff --git a/proxyd/proxyd/tools/mockserver/handler/handler.go b/proxyd/proxyd/tools/mockserver/handler/handler.go index 04f30e7..8a78a7e 100644 --- a/proxyd/proxyd/tools/mockserver/handler/handler.go +++ b/proxyd/proxyd/tools/mockserver/handler/handler.go @@ -95,7 +95,7 @@ func (mh *MockedHandler) Handler(w http.ResponseWriter, req *http.Request) { resBody := "" if batched { resBody = "[" + strings.Join(responses, ",") + "]" - } else { + } else if len(responses) > 0 { resBody = responses[0] } From 2d9259ee2006716f7a485f70c073399ad40e1584 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 26 May 2023 14:22:50 -0700 Subject: [PATCH 106/212] better moar tests --- proxyd/proxyd/backend.go | 32 +- proxyd/proxyd/consensus_poller.go | 67 +- .../integration_tests/consensus_test.go | 983 ++++++------------ .../testdata/consensus_responses.yml | 78 +- proxyd/proxyd/metrics.go | 97 +- proxyd/proxyd/rewriter.go | 2 +- 6 files changed, 533 insertions(+), 726 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 3946995..6ba7a10 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -374,7 +374,6 @@ func (b *Backend) ForwardRPC(ctx context.Context, res *RPCRes, id string, method func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { // we are concerned about network error rates, so we record 1 request independently of how many are in the batch b.networkRequestsSlidingWindow.Incr() - RecordBackendNetworkRequestCountSlidingWindow(b, b.networkRequestsSlidingWindow.Count()) isSingleElementBatch := len(rpcReqs) == 1 @@ -391,7 +390,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool httpReq, err := http.NewRequestWithContext(ctx, "POST", b.rpcURL, bytes.NewReader(body)) if err != nil { b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, wrapErr(err, "error creating backend request") } @@ -413,7 +412,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool httpRes, err := b.client.DoLimited(httpReq) if err != nil { b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, wrapErr(err, "error in backend request") } @@ -432,7 +431,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // Alchemy returns a 400 on bad JSONs, so handle that case if httpRes.StatusCode != 200 && httpRes.StatusCode != 400 { b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, fmt.Errorf("response code %d", httpRes.StatusCode) } @@ -440,7 +439,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool resB, err := io.ReadAll(io.LimitReader(httpRes.Body, b.maxResponseSize)) if err != nil { b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, wrapErr(err, "error reading response body") } @@ -458,18 +457,18 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // Infura may return a single JSON-RPC response if, for example, the batch contains a request for an unsupported method if responseIsNotBatched(resB) { b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, ErrBackendUnexpectedJSONRPC } b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, ErrBackendBadResponse } } if len(rpcReqs) != len(res) { b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorCountSlidingWindow(b, b.networkErrorsSlidingWindow.Count()) + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, ErrBackendUnexpectedJSONRPC } @@ -483,6 +482,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool duration := time.Since(start) b.latencySlidingWindow.Add(float64(duration)) RecordBackendNetworkLatencyAverageSlidingWindow(b, time.Duration(b.latencySlidingWindow.Avg())) + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) sortBatchRPCResponse(rpcReqs, res) return res, nil @@ -490,11 +490,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // IsHealthy checks if the backend is able to serve traffic, based on dynamic parameters func (b *Backend) IsHealthy() bool { - errorRate := float64(0) - // avoid division-by-zero when the window is empty - if b.networkRequestsSlidingWindow.Sum() >= 10 { - errorRate = b.networkErrorsSlidingWindow.Sum() / b.networkRequestsSlidingWindow.Sum() - } + errorRate := b.ErrorRate() avgLatency := time.Duration(b.latencySlidingWindow.Avg()) if errorRate >= b.maxErrorRateThreshold { return false @@ -505,6 +501,16 @@ func (b *Backend) IsHealthy() bool { return true } +// ErrorRate returns the instant error rate of the backend +func (b *Backend) ErrorRate() (errorRate float64) { + // we only really start counting the error rate after a minimum of 10 requests + // this is to avoid false positives when the backend is just starting up + if b.networkRequestsSlidingWindow.Sum() >= 10 { + errorRate = b.networkErrorsSlidingWindow.Sum() / b.networkRequestsSlidingWindow.Sum() + } + return errorRate +} + // IsDegraded checks if the backend is serving traffic in a degraded state (i.e. used as a last resource) func (b *Backend) IsDegraded() bool { avgLatency := time.Duration(b.latencySlidingWindow.Avg()) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 5c5edcd..1856566 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -275,23 +275,42 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { log.Warn("error updating backend", "name", be.Name, "err", err) } - finalizedBlockNumber, _, err := cp.fetchBlock(ctx, be, "finalized") - if err != nil { - log.Warn("error updating backend", "name", be.Name, "err", err) - } - safeBlockNumber, _, err := cp.fetchBlock(ctx, be, "safe") if err != nil { log.Warn("error updating backend", "name", be.Name, "err", err) } + finalizedBlockNumber, _, err := cp.fetchBlock(ctx, be, "finalized") + if err != nil { + log.Warn("error updating backend", "name", be.Name, "err", err) + } + + _, _, _, _, oldFinalized, oldSafe, _, _ := cp.getBackendState(be) + expectedBlockTags := cp.checkExpectedBlockTags(finalizedBlockNumber, oldFinalized, safeBlockNumber, oldSafe, latestBlockNumber) + changed, updateDelay := cp.setBackendState(be, peerCount, inSync, latestBlockNumber, latestBlockHash, finalizedBlockNumber, safeBlockNumber) + RecordBackendLatestBlock(be, latestBlockNumber) + RecordBackendSafeBlock(be, safeBlockNumber) + RecordBackendFinalizedBlock(be, finalizedBlockNumber) + RecordBackendUnexpectedBlockTags(be, !expectedBlockTags) + RecordConsensusBackendUpdateDelay(be, updateDelay) + + if !expectedBlockTags { + log.Warn("backend banned - unexpected block tags", + "backend", be.Name, + "oldFinalized", oldFinalized, + "finalizedBlockNumber", finalizedBlockNumber, + "oldSafe", oldSafe, + "safeBlockNumber", safeBlockNumber, + "latestBlockNumber", latestBlockNumber, + ) + cp.Ban(be) + } + if changed { - RecordBackendLatestBlock(be, latestBlockNumber) - RecordConsensusBackendUpdateDelay(be, updateDelay) log.Debug("backend state updated", "name", be.Name, "peerCount", peerCount, @@ -304,6 +323,19 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { } } +// checkExpectedBlockTags for unexpected conditions on block tags +// - finalized block number should never decrease +// - safe block number should never decrease +// - finalized block should be < safe block < latest block +func (cp *ConsensusPoller) checkExpectedBlockTags(currentFinalized hexutil.Uint64, oldFinalized hexutil.Uint64, + currentSafe hexutil.Uint64, oldSafe hexutil.Uint64, + currentLatest hexutil.Uint64) bool { + return currentFinalized >= oldFinalized && + currentSafe >= oldSafe && + currentFinalized <= currentSafe && + currentSafe <= currentLatest +} + // UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { var highestLatestBlock hexutil.Uint64 @@ -320,6 +352,9 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { for _, be := range cp.backendGroup.Backends { peerCount, inSync, backendLatestBlockNumber, _, _, _, lastUpdate, _ := cp.getBackendState(be) + if cp.IsBanned(be) { + continue + } if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { continue } @@ -339,6 +374,9 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { for _, be := range cp.backendGroup.Backends { peerCount, inSync, backendLatestBlockNumber, backendLatestBlockHash, backendFinalizedBlockNumber, backendSafeBlockNumber, lastUpdate, _ := cp.getBackendState(be) + if cp.IsBanned(be) { + continue + } if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { continue } @@ -451,13 +489,17 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { } cp.tracker.SetLatestBlockNumber(proposedBlock) - cp.tracker.SetFinalizedBlockNumber(lowestFinalizedBlock) cp.tracker.SetSafeBlockNumber(lowestSafeBlock) + cp.tracker.SetFinalizedBlockNumber(lowestFinalizedBlock) + cp.consensusGroupMux.Lock() cp.consensusGroup = consensusBackends cp.consensusGroupMux.Unlock() RecordGroupConsensusLatestBlock(cp.backendGroup, proposedBlock) + RecordGroupConsensusSafeBlock(cp.backendGroup, lowestSafeBlock) + RecordGroupConsensusFinalizedBlock(cp.backendGroup, lowestFinalizedBlock) + RecordGroupConsensusCount(cp.backendGroup, len(consensusBackends)) RecordGroupConsensusFilteredCount(cp.backendGroup, len(filteredBackendsNames)) RecordGroupTotalCount(cp.backendGroup, len(cp.backendGroup.Backends)) @@ -481,13 +523,10 @@ func (cp *ConsensusPoller) Ban(be *Backend) { bs.bannedUntil = time.Now().Add(cp.banPeriod) } -// Unban remove any bans from the backends -func (cp *ConsensusPoller) Unban() { +// Reset remove any bans from the backends and reset their states +func (cp *ConsensusPoller) Reset() { for _, be := range cp.backendGroup.Backends { - bs := cp.backendState[be] - bs.backendStateMux.Lock() - bs.bannedUntil = time.Now().Add(-10 * time.Hour) - bs.backendStateMux.Unlock() + cp.backendState[be] = &backendState{} } } diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index d95903e..fe36a15 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -16,6 +16,12 @@ import ( "github.com/stretchr/testify/require" ) +type nodeContext struct { + mockBackend *MockBackend + backend *proxyd.Backend + handler *ms.MockedHandler +} + func TestConsensus(t *testing.T) { node1 := NewMockBackend(nil) defer node1.Close() @@ -55,526 +61,402 @@ func TestConsensus(t *testing.T) { require.NotNil(t, bg) require.NotNil(t, bg.Consensus) + // convenient mapping to access the nodes by name + nodes := map[string]nodeContext{ + "node1": { + mockBackend: node1, + backend: bg.Backends[0], + handler: &h1, + }, + "node2": { + mockBackend: node2, + backend: bg.Backends[1], + handler: &h2, + }, + } + + reset := func() { + for _, node := range nodes { + node.handler.ResetOverrides() + node.mockBackend.Reset() + } + bg.Consensus.Reset() + } + + // poll for updated consensus + update := func() { + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + } + + override := func(node string, method string, block string, response string) { + nodes[node].handler.AddOverride(&ms.MethodTemplate{ + Method: method, + Block: block, + Response: response, + }) + } + + overrideBlock := func(node string, blockRequest string, blockResponse string) { + override(node, + "eth_getBlockByNumber", + blockRequest, + buildResponse(map[string]string{ + "number": blockResponse, + "hash": "hash_" + blockResponse, + })) + } + + overrideBlockHash := func(node string, blockRequest string, number string, hash string) { + override(node, + "eth_getBlockByNumber", + blockRequest, + buildResponse(map[string]string{ + "number": number, + "hash": hash, + })) + } + + overridePeerCount := func(node string, count int) { + override(node, "net_peerCount", "", buildResponse(hexutil.Uint64(count).String())) + } + + overrideNotInSync := func(node string) { + override(node, "eth_syncing", "", buildResponse(map[string]string{ + "startingblock": "0x0", + "currentblock": "0x0", + "highestblock": "0x100", + })) + } + + useOnlyNode1 := func() { + overridePeerCount("node2", 0) + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.Equal(t, 1, len(consensusGroup)) + require.Contains(t, consensusGroup, nodes["node1"].backend) + node1.Reset() + } + t.Run("initial consensus", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() // unknown consensus at init require.Equal(t, "0x0", bg.Consensus.GetLatestBlockNumber().String()) // first poll - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + update() - // consensus at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0x555", bg.Consensus.GetFinalizedBlockNumber().String()) - require.Equal(t, "0x551", bg.Consensus.GetSafeBlockNumber().String()) + // as a default we use: + // - latest at 0x101 [257] + // - safe at 0xe1 [225] + // - finalized at 0xc1 [193] + + // consensus at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) }) t.Run("prevent using a backend with low peer count", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() + overridePeerCount("node1", 0) + update() - h1.AddOverride(&ms.MethodTemplate{ - Method: "net_peerCount", - Block: "", - Response: buildPeerCountResponse(1), - }) - - be := backend(bg, "node1") - require.NotNil(t, be) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) consensusGroup := bg.Consensus.GetConsensusGroup() - - require.NotContains(t, consensusGroup, be) - require.False(t, bg.Consensus.IsBanned(be)) + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) require.Equal(t, 1, len(consensusGroup)) }) t.Run("prevent using a backend lagging behind", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() + // node2 is 51 blocks ahead of node1 (0x101 + 51 = 0x134) + overrideBlock("node2", "latest", "0x134") + update() - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x1", "hash1"), - }) - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "finalized", - Response: buildGetBlockResponse("0x1", "hash1"), - }) - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "safe", - Response: buildGetBlockResponse("0x1", "hash1"), - }) - - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x100", "hash0x100"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x100", - Response: buildGetBlockResponse("0x100", "hash0x100"), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - // since we ignored node1, the consensus should be at 0x100 - require.Equal(t, "0x100", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0x555", bg.Consensus.GetFinalizedBlockNumber().String()) - require.Equal(t, "0x551", bg.Consensus.GetSafeBlockNumber().String()) + // since we ignored node1, the consensus should be at 0x133 + require.Equal(t, "0x134", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) consensusGroup := bg.Consensus.GetConsensusGroup() - - be := backend(bg, "node1") - require.NotNil(t, be) - require.NotContains(t, consensusGroup, be) - require.False(t, bg.Consensus.IsBanned(be)) + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) require.Equal(t, 1, len(consensusGroup)) }) - t.Run("prevent using a backend lagging behind - at limit", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() - - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x1", "hash1"), - }) - - // 0x1 + 50 = 0x33 - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x33", "hash0x100"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x100", - Response: buildGetBlockResponse("0x33", "hash0x100"), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - // since we ignored node1, the consensus should be at 0x100 - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) - - consensusGroup := bg.Consensus.GetConsensusGroup() - - require.Equal(t, 2, len(consensusGroup)) - }) - t.Run("prevent using a backend lagging behind - one before limit", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() + // node2 is 50 blocks ahead of node1 (0x101 + 50 = 0x133) + overrideBlock("node2", "latest", "0x133") + update() - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x1", "hash1"), - }) - - // 0x1 + 49 = 0x32 - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x32", "hash0x100"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x100", - Response: buildGetBlockResponse("0x32", "hash0x100"), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) - - consensusGroup := bg.Consensus.GetConsensusGroup() - - require.Equal(t, 2, len(consensusGroup)) + // both nodes are in consensus with the lowest block + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) }) t.Run("prevent using a backend not in sync", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() + // make node1 not in sync + overrideNotInSync("node1") + update() - // advance latest on node2 to 0x2 - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_syncing", - Block: "", - Response: buildResponse(map[string]string{ - "startingblock": "0x0", - "currentblock": "0x0", - "highestblock": "0x100", - }), - }) - - be := backend(bg, "node1") - require.NotNil(t, be) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) consensusGroup := bg.Consensus.GetConsensusGroup() - - require.NotContains(t, consensusGroup, be) - require.False(t, bg.Consensus.IsBanned(be)) + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) require.Equal(t, 1, len(consensusGroup)) }) t.Run("advance consensus", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } + // as a default we use: + // - latest at 0x101 [257] + // - safe at 0xe1 [225] + // - finalized at 0xc1 [193] + + update() + + // all nodes start at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // advance latest on node2 to 0x102 + overrideBlock("node2", "latest", "0x102") + + update() + + // consensus should stick to 0x101, since node1 is still lagging there bg.Consensus.UpdateBackendGroupConsensus(ctx) + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) + // advance latest on node1 to 0x102 + overrideBlock("node1", "latest", "0x102") - // advance latest on node2 to 0x2 - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) + update() - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - - // consensus should stick to 0x1, since node1 is still lagging there - bg.Consensus.UpdateBackendGroupConsensus(ctx) - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) - - // advance latest on node1 to 0x2 - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) - - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - // should stick to 0x2, since now all nodes are at 0x2 - require.Equal(t, "0x2", bg.Consensus.GetLatestBlockNumber().String()) + // all nodes now at 0x102 + require.Equal(t, "0x102", bg.Consensus.GetLatestBlockNumber().String()) }) t.Run("should use lowest safe and finalized", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() + overrideBlock("node2", "finalized", "0xc2") + overrideBlock("node2", "safe", "0xe2") + update() - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "finalized", - Response: buildGetBlockResponse("0x559", "hash559"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "safe", - Response: buildGetBlockResponse("0x558", "hash558"), - }) - - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - require.Equal(t, "0x555", bg.Consensus.GetFinalizedBlockNumber().String()) - require.Equal(t, "0x551", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) }) t.Run("advance safe and finalized", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() + overrideBlock("node1", "finalized", "0xc2") + overrideBlock("node1", "safe", "0xe2") + overrideBlock("node2", "finalized", "0xc2") + overrideBlock("node2", "safe", "0xe2") + update() - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + require.Equal(t, "0xc2", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0xe2", bg.Consensus.GetSafeBlockNumber().String()) + }) - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "finalized", - Response: buildGetBlockResponse("0x556", "hash556"), - }) - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "safe", - Response: buildGetBlockResponse("0x552", "hash552"), - }) + t.Run("ban backend if tags are messed - safe < finalized", func(t *testing.T) { + reset() + overrideBlock("node1", "finalized", "0xb1") + overrideBlock("node1", "safe", "0xa1") + update() - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "finalized", - Response: buildGetBlockResponse("0x559", "hash559"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "safe", - Response: buildGetBlockResponse("0x558", "hash558"), - }) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) - bg.Consensus.UpdateBackendGroupConsensus(ctx) + t.Run("ban backend if tags are messed - latest < safe", func(t *testing.T) { + reset() + overrideBlock("node1", "safe", "0xb1") + overrideBlock("node1", "latest", "0xa1") + update() - require.Equal(t, "0x556", bg.Consensus.GetFinalizedBlockNumber().String()) - require.Equal(t, "0x552", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("ban backend if tags are messed - safe dropped", func(t *testing.T) { + reset() + update() + overrideBlock("node1", "safe", "0xb1") + update() + + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("ban backend if tags are messed - finalized dropped", func(t *testing.T) { + reset() + update() + overrideBlock("node1", "finalized", "0xa1") + update() + + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) }) t.Run("broken consensus", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() - + reset() listenerCalled := false bg.Consensus.AddListener(func() { listenerCalled = true }) + update() - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + // all nodes start at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) + // advance latest on both nodes to 0x102 + overrideBlock("node1", "latest", "0x102") + overrideBlock("node2", "latest", "0x102") - // advance latest on both nodes to 0x2 - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) + update() - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - // at 0x2 - require.Equal(t, "0x2", bg.Consensus.GetLatestBlockNumber().String()) + // at 0x102 + require.Equal(t, "0x102", bg.Consensus.GetLatestBlockNumber().String()) // make node2 diverge on hash - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x2", - Response: buildGetBlockResponse("0x2", "wrong_hash"), - }) + overrideBlockHash("node2", "0x102", "0x102", "wrong_hash") - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + update() - // should resolve to 0x1, since 0x2 is out of consensus at the moment - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) + // should resolve to 0x101, since 0x102 is out of consensus at the moment + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + // everybody serving traffic + consensusGroup := bg.Consensus.GetConsensusGroup() + require.Equal(t, 2, len(consensusGroup)) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.False(t, bg.Consensus.IsBanned(nodes["node2"].backend)) + + // onConsensusBroken listener was called require.True(t, listenerCalled) }) t.Run("broken consensus with depth 2", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) - - // advance latest on both nodes to 0x2 - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), + reset() + listenerCalled := false + bg.Consensus.AddListener(func() { + listenerCalled = true }) + update() - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + // all nodes start at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - // at 0x2 - require.Equal(t, "0x2", bg.Consensus.GetLatestBlockNumber().String()) + // advance latest on both nodes to 0x102 + overrideBlock("node1", "latest", "0x102") + overrideBlock("node2", "latest", "0x102") + + update() + + // at 0x102 + require.Equal(t, "0x102", bg.Consensus.GetLatestBlockNumber().String()) // advance latest on both nodes to 0x3 - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x3", "hash3"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x3", "hash3"), - }) + overrideBlock("node1", "latest", "0x103") + overrideBlock("node2", "latest", "0x103") - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + update() - // at 0x3 - require.Equal(t, "0x3", bg.Consensus.GetLatestBlockNumber().String()) + // at 0x103 + require.Equal(t, "0x103", bg.Consensus.GetLatestBlockNumber().String()) - // make node2 diverge on hash for blocks 0x2 and 0x3 - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x2", - Response: buildGetBlockResponse("0x2", "wrong_hash2"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x3", - Response: buildGetBlockResponse("0x3", "wrong_hash3"), - }) + // make node2 diverge on hash for blocks 0x102 and 0x103 + overrideBlockHash("node2", "0x102", "0x102", "wrong_hash_0x102") + overrideBlockHash("node2", "0x103", "0x103", "wrong_hash_0x103") - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + update() - // should resolve to 0x1 - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) + // should resolve to 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // everybody serving traffic + consensusGroup := bg.Consensus.GetConsensusGroup() + require.Equal(t, 2, len(consensusGroup)) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.False(t, bg.Consensus.IsBanned(nodes["node2"].backend)) + + // onConsensusBroken listener was called + require.True(t, listenerCalled) }) t.Run("fork in advanced block", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() + listenerCalled := false + bg.Consensus.AddListener(func() { + listenerCalled = true + }) + update() - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + // all nodes start at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - // all nodes start at block 0x1 - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) + // make nodes 1 and 2 advance in forks, i.e. they have same block number with different hashes + overrideBlockHash("node1", "0x102", "0x102", "node1_0x102") + overrideBlockHash("node2", "0x102", "0x102", "node2_0x102") + overrideBlockHash("node1", "0x103", "0x103", "node1_0x103") + overrideBlockHash("node2", "0x103", "0x103", "node2_0x103") + overrideBlockHash("node1", "latest", "0x103", "node1_0x103") + overrideBlockHash("node2", "latest", "0x103", "node2_0x103") - // make nodes 1 and 2 advance in forks - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x2", - Response: buildGetBlockResponse("0x2", "node1_0x2"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x2", - Response: buildGetBlockResponse("0x2", "node2_0x2"), - }) - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x3", - Response: buildGetBlockResponse("0x3", "node1_0x3"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "0x3", - Response: buildGetBlockResponse("0x3", "node2_0x3"), - }) - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x3", "node1_0x3"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x3", "node2_0x3"), - }) + update() - // poll for group consensus - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + // should resolve to 0x101, the highest common ancestor + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - // should resolve to 0x1, the highest common ancestor - require.Equal(t, "0x1", bg.Consensus.GetLatestBlockNumber().String()) + // everybody serving traffic + consensusGroup := bg.Consensus.GetConsensusGroup() + require.Equal(t, 2, len(consensusGroup)) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.False(t, bg.Consensus.IsBanned(nodes["node2"].backend)) + + // onConsensusBroken listener should not be called + require.False(t, listenerCalled) }) t.Run("load balancing should hit both backends", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + reset() + update() require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) + // reset request counts node1.Reset() node2.Reset() @@ -591,7 +473,7 @@ func TestConsensus(t *testing.T) { numberReqs := len(consensusGroup) * 100 for numberReqs > 0 { - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x1", false}) + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) require.NoError(t, err) require.Equal(t, 200, statusCode) numberReqs-- @@ -603,24 +485,10 @@ func TestConsensus(t *testing.T) { }) t.Run("load balancing should not hit if node is not healthy", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() - - // node1 should not be serving any traffic - h1.AddOverride(&ms.MethodTemplate{ - Method: "net_peerCount", - Block: "", - Response: buildPeerCountResponse(1), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + reset() + useOnlyNode1() + // reset request counts node1.Reset() node2.Reset() @@ -629,60 +497,24 @@ func TestConsensus(t *testing.T) { numberReqs := 10 for numberReqs > 0 { - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x1", false}) + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) require.NoError(t, err) require.Equal(t, 200, statusCode) numberReqs-- } msg := fmt.Sprintf("n1 %d, n2 %d", len(node1.Requests()), len(node2.Requests())) - require.Equal(t, len(node1.Requests()), 0, msg) - require.Equal(t, len(node2.Requests()), 10, msg) + require.Equal(t, len(node1.Requests()), 10, msg) + require.Equal(t, len(node2.Requests()), 0, msg) }) t.Run("rewrite response of eth_blockNumber", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - node1.Reset() - node2.Reset() - bg.Consensus.Unban() - - // establish the consensus - - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) + reset() + update() totalRequests := len(node1.Requests()) + len(node2.Requests()) - require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) - // pretend backends advanced in consensus, but we are still serving the latest value of the consensus - // until it gets updated again - - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x3", "hash3"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x3", "hash3"), - }) - resRaw, statusCode, err := client.SendRPC("eth_blockNumber", nil) require.NoError(t, err) require.Equal(t, 200, statusCode) @@ -690,37 +522,15 @@ func TestConsensus(t *testing.T) { var jsonMap map[string]interface{} err = json.Unmarshal(resRaw, &jsonMap) require.NoError(t, err) - require.Equal(t, "0x2", jsonMap["result"]) + require.Equal(t, "0x101", jsonMap["result"]) // no extra request hit the backends require.Equal(t, totalRequests, len(node1.Requests())+len(node2.Requests())) }) t.Run("rewrite request of eth_getBlockByNumber for latest", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() - - // establish the consensus and ban node2 for now - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "net_peerCount", - Block: "", - Response: buildPeerCountResponse(1), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - - node1.Reset() + reset() + useOnlyNode1() _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"latest"}) require.NoError(t, err) @@ -729,39 +539,12 @@ func TestConsensus(t *testing.T) { var jsonMap map[string]interface{} err = json.Unmarshal(node1.Requests()[0].Body, &jsonMap) require.NoError(t, err) - require.Equal(t, "0x2", jsonMap["params"].([]interface{})[0]) + require.Equal(t, "0x101", jsonMap["params"].([]interface{})[0]) }) t.Run("rewrite request of eth_getBlockByNumber for finalized", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() - - // establish the consensus and ban node2 for now - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x20", "hash20"), - }) - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "finalized", - Response: buildGetBlockResponse("0x5", "hash5"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "net_peerCount", - Block: "", - Response: buildPeerCountResponse(1), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - - node1.Reset() + reset() + useOnlyNode1() _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"finalized"}) require.NoError(t, err) @@ -770,39 +553,12 @@ func TestConsensus(t *testing.T) { var jsonMap map[string]interface{} err = json.Unmarshal(node1.Requests()[0].Body, &jsonMap) require.NoError(t, err) - require.Equal(t, "0x5", jsonMap["params"].([]interface{})[0]) + require.Equal(t, "0xc1", jsonMap["params"].([]interface{})[0]) }) t.Run("rewrite request of eth_getBlockByNumber for safe", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() - - // establish the consensus and ban node2 for now - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x20", "hash20"), - }) - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "safe", - Response: buildGetBlockResponse("0x1", "hash1"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "net_peerCount", - Block: "", - Response: buildPeerCountResponse(1), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - - node1.Reset() + reset() + useOnlyNode1() _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"safe"}) require.NoError(t, err) @@ -811,36 +567,14 @@ func TestConsensus(t *testing.T) { var jsonMap map[string]interface{} err = json.Unmarshal(node1.Requests()[0].Body, &jsonMap) require.NoError(t, err) - require.Equal(t, "0x1", jsonMap["params"].([]interface{})[0]) + require.Equal(t, "0xe1", jsonMap["params"].([]interface{})[0]) }) t.Run("rewrite request of eth_getBlockByNumber - out of range", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() + reset() + useOnlyNode1() - // establish the consensus and ban node2 for now - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "net_peerCount", - Block: "", - Response: buildPeerCountResponse(1), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - - node1.Reset() - - resRaw, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x10"}) + resRaw, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x300"}) require.NoError(t, err) require.Equal(t, 400, statusCode) @@ -852,35 +586,13 @@ func TestConsensus(t *testing.T) { }) t.Run("batched rewrite", func(t *testing.T) { - h1.ResetOverrides() - h2.ResetOverrides() - bg.Consensus.Unban() - - // establish the consensus and ban node2 for now - h1.AddOverride(&ms.MethodTemplate{ - Method: "eth_getBlockByNumber", - Block: "latest", - Response: buildGetBlockResponse("0x2", "hash2"), - }) - h2.AddOverride(&ms.MethodTemplate{ - Method: "net_peerCount", - Block: "", - Response: buildPeerCountResponse(1), - }) - - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - - node1.Reset() + reset() + useOnlyNode1() resRaw, statusCode, err := client.SendBatchRPC( NewRPCReq("1", "eth_getBlockByNumber", []interface{}{"latest"}), - NewRPCReq("2", "eth_getBlockByNumber", []interface{}{"0x10"}), - NewRPCReq("3", "eth_getBlockByNumber", []interface{}{"0x1"})) + NewRPCReq("2", "eth_getBlockByNumber", []interface{}{"0x102"}), + NewRPCReq("3", "eth_getBlockByNumber", []interface{}{"0xe1"})) require.NoError(t, err) require.Equal(t, 200, statusCode) @@ -889,34 +601,15 @@ func TestConsensus(t *testing.T) { require.NoError(t, err) require.Equal(t, 3, len(jsonMap)) - // rewrite latest to 0x2 - require.Equal(t, "0x2", jsonMap[0]["result"].(map[string]interface{})["number"]) + // rewrite latest to 0x101 + require.Equal(t, "0x101", jsonMap[0]["result"].(map[string]interface{})["number"]) - // out of bounds for block 0x10 + // out of bounds for block 0x102 require.Equal(t, -32019, int(jsonMap[1]["error"].(map[string]interface{})["code"].(float64))) require.Equal(t, "block is out of range", jsonMap[1]["error"].(map[string]interface{})["message"]) - // dont rewrite for 0x1 - require.Equal(t, "0x1", jsonMap[2]["result"].(map[string]interface{})["number"]) - }) -} - -func backend(bg *proxyd.BackendGroup, name string) *proxyd.Backend { - for _, be := range bg.Backends { - if be.Name == name { - return be - } - } - return nil -} - -func buildPeerCountResponse(count uint64) string { - return buildResponse(hexutil.Uint64(count).String()) -} -func buildGetBlockResponse(number string, hash string) string { - return buildResponse(map[string]string{ - "number": number, - "hash": hash, + // dont rewrite for 0xe1 + require.Equal(t, "0xe1", jsonMap[2]["result"].(map[string]interface{})["number"]) }) } diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml index ef5b692..12cc4cd 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml +++ b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml @@ -26,63 +26,85 @@ "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash1", - "number": "0x1" + "hash": "hash_0x101", + "number": "0x101" } } - method: eth_getBlockByNumber - block: 0x1 + block: 0x101 response: > { "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash1", - "number": "0x1" + "hash": "hash_0x101", + "number": "0x101" } } - method: eth_getBlockByNumber - block: 0x2 + block: 0x102 response: > { "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash2", - "number": "0x2" + "hash": "hash_0x102", + "number": "0x102" } } - method: eth_getBlockByNumber - block: 0x3 + block: 0x103 response: > { "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash3", - "number": "0x3" + "hash": "hash_0x103", + "number": "0x103" } } - method: eth_getBlockByNumber - block: finalized + block: 0x132 response: > { "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash_finalized", - "number": "0x555" + "hash": "hash_0x132", + "number": "0x132" } } - method: eth_getBlockByNumber - block: 0x555 + block: 0x133 response: > { "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash_finalized", - "number": "0x555" + "hash": "hash_0x133", + "number": "0x133" + } + } +- method: eth_getBlockByNumber + block: 0x134 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x134", + "number": "0x134" + } + } +- method: eth_getBlockByNumber + block: 0x200 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x200", + "number": "0x200" } } - method: eth_getBlockByNumber @@ -92,40 +114,40 @@ "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash_safe", - "number": "0x551" + "hash": "hash_0xe1", + "number": "0xe1" } } - method: eth_getBlockByNumber - block: 0x555 + block: 0xe1 response: > { "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash_safe", - "number": "0x551" + "hash": "hash_0xe1", + "number": "0xe1" } } - method: eth_getBlockByNumber - block: 0x5 + block: finalized response: > { "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash5", - "number": "0x5" + "hash": "hash_0xc1", + "number": "0xc1" } } - method: eth_getBlockByNumber - block: 0x20 + block: 0xc1 response: > { "jsonrpc": "2.0", "id": 67, "result": { - "hash": "hash20", - "number": "0x20" + "hash": "hash_0xc1", + "number": "0xc1" } } diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 884e783..8c52ed4 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -246,6 +246,22 @@ var ( "backend_group_name", }) + consensusSafeBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_safe_block", + Help: "Consensus safe block", + }, []string{ + "backend_group_name", + }) + + consensusFinalizedBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_finalized_block", + Help: "Consensus finalized block", + }, []string{ + "backend_group_name", + }) + backendLatestBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, Name: "backend_latest_block", @@ -254,6 +270,30 @@ var ( "backend_name", }) + backendSafeBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_safe_block", + Help: "Current safe block observed per backend", + }, []string{ + "backend_name", + }) + + backendFinalizedBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_finalized_block", + Help: "Current finalized block observed per backend", + }, []string{ + "backend_name", + }) + + backendUnexpectedBlockTagsBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_unexpected_block_tags", + Help: "Bool gauge for unexpected block tags", + }, []string{ + "backend_name", + }) + consensusGroupCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, Name: "group_consensus_count", @@ -318,18 +358,10 @@ var ( "backend_name", }) - networkErrorCountBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + networkErrorRateBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, - Name: "backend_net_error_count", - Help: "Network error count per backend", - }, []string{ - "backend_name", - }) - - requestCountBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "backend_request_count", - Help: "Request count per backend", + Name: "backend_error_rate", + Help: "Request error rate per backend", }, []string{ "backend_name", }) @@ -402,6 +434,14 @@ func RecordGroupConsensusLatestBlock(group *BackendGroup, blockNumber hexutil.Ui consensusLatestBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) } +func RecordGroupConsensusSafeBlock(group *BackendGroup, blockNumber hexutil.Uint64) { + consensusSafeBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) +} + +func RecordGroupConsensusFinalizedBlock(group *BackendGroup, blockNumber hexutil.Uint64) { + consensusFinalizedBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) +} + func RecordGroupConsensusCount(group *BackendGroup, count int) { consensusGroupCount.WithLabelValues(group.Name).Set(float64(count)) } @@ -418,12 +458,20 @@ func RecordBackendLatestBlock(b *Backend, blockNumber hexutil.Uint64) { backendLatestBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) } +func RecordBackendSafeBlock(b *Backend, blockNumber hexutil.Uint64) { + backendSafeBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) +} + +func RecordBackendFinalizedBlock(b *Backend, blockNumber hexutil.Uint64) { + backendFinalizedBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) +} + +func RecordBackendUnexpectedBlockTags(b *Backend, unexpected bool) { + backendFinalizedBlockBackend.WithLabelValues(b.Name).Set(boolToFloat64(unexpected)) +} + func RecordConsensusBackendBanned(b *Backend, banned bool) { - v := float64(0) - if banned { - v = float64(1) - } - consensusBannedBackends.WithLabelValues(b.Name).Set(v) + consensusBannedBackends.WithLabelValues(b.Name).Set(boolToFloat64(banned)) } func RecordConsensusBackendPeerCount(b *Backend, peerCount uint64) { @@ -431,11 +479,7 @@ func RecordConsensusBackendPeerCount(b *Backend, peerCount uint64) { } func RecordConsensusBackendInSync(b *Backend, inSync bool) { - v := float64(0) - if inSync { - v = float64(1) - } - consensusInSyncBackend.WithLabelValues(b.Name).Set(v) + consensusInSyncBackend.WithLabelValues(b.Name).Set(boolToFloat64(inSync)) } func RecordConsensusBackendUpdateDelay(b *Backend, delay time.Duration) { @@ -446,10 +490,13 @@ func RecordBackendNetworkLatencyAverageSlidingWindow(b *Backend, avgLatency time avgLatencyBackend.WithLabelValues(b.Name).Set(float64(avgLatency.Milliseconds())) } -func RecordBackendNetworkRequestCountSlidingWindow(b *Backend, count uint) { - requestCountBackend.WithLabelValues(b.Name).Set(float64(count)) +func RecordBackendNetworkErrorRateSlidingWindow(b *Backend, rate float64) { + networkErrorRateBackend.WithLabelValues(b.Name).Set(rate) } -func RecordBackendNetworkErrorCountSlidingWindow(b *Backend, count uint) { - networkErrorCountBackend.WithLabelValues(b.Name).Set(float64(count)) +func boolToFloat64(b bool) float64 { + if b { + return 1 + } + return 0 } diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index ab098d8..08d5638 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -10,8 +10,8 @@ import ( type RewriteContext struct { latest hexutil.Uint64 - finalized hexutil.Uint64 safe hexutil.Uint64 + finalized hexutil.Uint64 } type RewriteResult uint8 From 83c40076b67540ddac8685f770f22515c1ef3440 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 26 May 2023 14:25:56 -0700 Subject: [PATCH 107/212] typo --- proxyd/proxyd/metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 8c52ed4..3d8bfac 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -467,7 +467,7 @@ func RecordBackendFinalizedBlock(b *Backend, blockNumber hexutil.Uint64) { } func RecordBackendUnexpectedBlockTags(b *Backend, unexpected bool) { - backendFinalizedBlockBackend.WithLabelValues(b.Name).Set(boolToFloat64(unexpected)) + backendUnexpectedBlockTagsBackend.WithLabelValues(b.Name).Set(boolToFloat64(unexpected)) } func RecordConsensusBackendBanned(b *Backend, banned bool) { From cdf9e9192a2f4369013d7eb76effc5917fd83ef0 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 26 May 2023 14:49:27 -0700 Subject: [PATCH 108/212] use go 1.20 --- proxyd/proxyd/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/Dockerfile b/proxyd/proxyd/Dockerfile index f6ba052..30b216f 100644 --- a/proxyd/proxyd/Dockerfile +++ b/proxyd/proxyd/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18.0-alpine3.15 as builder +FROM golang:1.20.4-alpine3.18 as builder ARG GITCOMMIT=docker ARG GITDATE=docker @@ -12,7 +12,7 @@ WORKDIR /app RUN make proxyd -FROM alpine:3.15 +FROM alpine:3.18 COPY ./proxyd/entrypoint.sh /bin/entrypoint.sh From d5594a62a0a48faa797a13e2474fe317d4659222 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 26 May 2023 15:54:04 -0700 Subject: [PATCH 109/212] moar tests for safe and finalized bans --- proxyd/proxyd/consensus_poller.go | 117 ++++++++++-------- .../integration_tests/consensus_test.go | 76 ++++++++++++ .../testdata/consensus_responses.yml | 22 ++++ 3 files changed, 161 insertions(+), 54 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 1856566..1ad4b22 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -406,11 +406,6 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { } } - // no block to propose (i.e. initializing consensus) - if lowestLatestBlock == 0 { - return - } - proposedBlock := lowestLatestBlock proposedBlockHash := lowestLatestBlockHash hasConsensus := false @@ -424,59 +419,63 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { log.Debug("validating consensus on block", "lowestLatestBlock", lowestLatestBlock) } - broken := false - for !hasConsensus { - allAgreed := true - consensusBackends = consensusBackends[:0] - filteredBackendsNames = filteredBackendsNames[:0] - for _, be := range cp.backendGroup.Backends { - /* - a serving node needs to be: - - healthy (network) - - updated recently - - not banned - - with minimum peer count - - not lagging latest block - - in sync - */ + // if there is no block to propose, the consensus is automatically broken + broken := proposedBlock == 0 && currentConsensusBlockNumber > 0 - peerCount, inSync, latestBlockNumber, _, _, _, lastUpdate, bannedUntil := cp.getBackendState(be) - notUpdated := lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) - isBanned := time.Now().Before(bannedUntil) - notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount - lagging := latestBlockNumber < proposedBlock - if !be.IsHealthy() || notUpdated || isBanned || notEnoughPeers || lagging || !inSync { - filteredBackendsNames = append(filteredBackendsNames, be.Name) - continue - } + if proposedBlock > 0 { + for !hasConsensus { + allAgreed := true + consensusBackends = consensusBackends[:0] + filteredBackendsNames = filteredBackendsNames[:0] + for _, be := range cp.backendGroup.Backends { + /* + a serving node needs to be: + - healthy (network) + - updated recently + - not banned + - with minimum peer count + - not lagging latest block + - in sync + */ - actualBlockNumber, actualBlockHash, err := cp.fetchBlock(ctx, be, proposedBlock.String()) - if err != nil { - log.Warn("error updating backend", "name", be.Name, "err", err) - continue - } - if proposedBlockHash == "" { - proposedBlockHash = actualBlockHash - } - blocksDontMatch := (actualBlockNumber != proposedBlock) || (actualBlockHash != proposedBlockHash) - if blocksDontMatch { - if currentConsensusBlockNumber >= actualBlockNumber { - log.Warn("backend broke consensus", "name", be.Name, "blockNum", actualBlockNumber, "proposedBlockNum", proposedBlock, "blockHash", actualBlockHash, "proposedBlockHash", proposedBlockHash) - broken = true + peerCount, inSync, latestBlockNumber, _, _, _, lastUpdate, bannedUntil := cp.getBackendState(be) + notUpdated := lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) + isBanned := time.Now().Before(bannedUntil) + notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount + lagging := latestBlockNumber < proposedBlock + if !be.IsHealthy() || notUpdated || isBanned || notEnoughPeers || lagging || !inSync { + filteredBackendsNames = append(filteredBackendsNames, be.Name) + continue } - allAgreed = false - break + + actualBlockNumber, actualBlockHash, err := cp.fetchBlock(ctx, be, proposedBlock.String()) + if err != nil { + log.Warn("error updating backend", "name", be.Name, "err", err) + continue + } + if proposedBlockHash == "" { + proposedBlockHash = actualBlockHash + } + blocksDontMatch := (actualBlockNumber != proposedBlock) || (actualBlockHash != proposedBlockHash) + if blocksDontMatch { + if currentConsensusBlockNumber >= actualBlockNumber { + log.Warn("backend broke consensus", "name", be.Name, "blockNum", actualBlockNumber, "proposedBlockNum", proposedBlock, "blockHash", actualBlockHash, "proposedBlockHash", proposedBlockHash) + broken = true + } + allAgreed = false + break + } + consensusBackends = append(consensusBackends, be) + consensusBackendsNames = append(consensusBackendsNames, be.Name) + } + if allAgreed { + hasConsensus = true + } else { + // walk one block behind and try again + proposedBlock -= 1 + proposedBlockHash = "" + log.Debug("no consensus, now trying", "block:", proposedBlock) } - consensusBackends = append(consensusBackends, be) - consensusBackendsNames = append(consensusBackendsNames, be.Name) - } - if allAgreed { - hasConsensus = true - } else { - // walk one block behind and try again - proposedBlock -= 1 - proposedBlockHash = "" - log.Debug("no consensus, now trying", "block:", proposedBlock) } } @@ -521,6 +520,16 @@ func (cp *ConsensusPoller) Ban(be *Backend) { defer bs.backendStateMux.Unlock() bs.backendStateMux.Lock() bs.bannedUntil = time.Now().Add(cp.banPeriod) + bs.safeBlockNumber = 0 + bs.finalizedBlockNumber = 0 +} + +// Unban remove any bans from the backends +func (cp *ConsensusPoller) Unban(be *Backend) { + bs := cp.backendState[be] + defer bs.backendStateMux.Unlock() + bs.backendStateMux.Lock() + bs.bannedUntil = time.Now().Add(-10 * time.Hour) } // Reset remove any bans from the backends and reset their states diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index fe36a15..320139a 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -330,6 +330,82 @@ func TestConsensus(t *testing.T) { require.Equal(t, 1, len(consensusGroup)) }) + t.Run("recover after safe and finalized dropped", func(t *testing.T) { + reset() + useOnlyNode1() + overrideBlock("node1", "latest", "0xd1") + overrideBlock("node1", "safe", "0xb1") + overrideBlock("node1", "finalized", "0x91") + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + + // unban and see if it recovers + bg.Consensus.Unban(nodes["node1"].backend) + update() + + consensusGroup = bg.Consensus.GetConsensusGroup() + require.Contains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + + require.Equal(t, "0xd1", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0xb1", bg.Consensus.GetSafeBlockNumber().String()) + }) + + t.Run("latest dropped below safe, then recovered", func(t *testing.T) { + reset() + useOnlyNode1() + overrideBlock("node1", "latest", "0xd1") + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + + // unban and see if it recovers + bg.Consensus.Unban(nodes["node1"].backend) + overrideBlock("node1", "safe", "0xb1") + overrideBlock("node1", "finalized", "0x91") + update() + + consensusGroup = bg.Consensus.GetConsensusGroup() + require.Contains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + + require.Equal(t, "0xd1", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0xb1", bg.Consensus.GetSafeBlockNumber().String()) + }) + + t.Run("latest dropped below safe, and stayed inconsistent after ban", func(t *testing.T) { + reset() + useOnlyNode1() + overrideBlock("node1", "latest", "0xd1") + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + + // unban and see if it recovers - it should not since the blocks stays the same + bg.Consensus.Unban(nodes["node1"].backend) + update() + + // should be banned again + consensusGroup = bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + }) + t.Run("broken consensus", func(t *testing.T) { reset() listenerCalled := false diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml index 12cc4cd..ea8ef72 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml +++ b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml @@ -107,6 +107,17 @@ "number": "0x200" } } +- method: eth_getBlockByNumber + block: 0x91 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x91", + "number": "0x91" + } + } - method: eth_getBlockByNumber block: safe response: > @@ -151,3 +162,14 @@ "number": "0xc1" } } +- method: eth_getBlockByNumber + block: 0xd1 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0xd1", + "number": "0xd1" + } + } From 32be28788759d820bc6f8c510682378bc560411c Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 26 May 2023 15:57:22 -0700 Subject: [PATCH 110/212] typo --- proxyd/proxyd/consensus_poller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 1ad4b22..d019951 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -326,7 +326,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { // checkExpectedBlockTags for unexpected conditions on block tags // - finalized block number should never decrease // - safe block number should never decrease -// - finalized block should be < safe block < latest block +// - finalized block should be <= safe block <= latest block func (cp *ConsensusPoller) checkExpectedBlockTags(currentFinalized hexutil.Uint64, oldFinalized hexutil.Uint64, currentSafe hexutil.Uint64, oldSafe hexutil.Uint64, currentLatest hexutil.Uint64) bool { From 3cdac70a0de21957ed537c3d03ccb63d99383d29 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 26 May 2023 19:07:35 -0700 Subject: [PATCH 111/212] reset listeners, doc typos --- proxyd/proxyd/consensus_poller.go | 12 ++++++++---- proxyd/proxyd/integration_tests/consensus_test.go | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index d019951..5aff27e 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -177,6 +177,10 @@ func (cp *ConsensusPoller) AddListener(listener OnConsensusBroken) { cp.listeners = append(cp.listeners, listener) } +func (cp *ConsensusPoller) ClearListeners() { + cp.listeners = []OnConsensusBroken{} +} + func WithBanPeriod(banPeriod time.Duration) ConsensusOpt { return func(cp *ConsensusPoller) { cp.banPeriod = banPeriod @@ -524,7 +528,7 @@ func (cp *ConsensusPoller) Ban(be *Backend) { bs.finalizedBlockNumber = 0 } -// Unban remove any bans from the backends +// Unban removes any bans from the backends func (cp *ConsensusPoller) Unban(be *Backend) { bs := cp.backendState[be] defer bs.backendStateMux.Unlock() @@ -532,14 +536,14 @@ func (cp *ConsensusPoller) Unban(be *Backend) { bs.bannedUntil = time.Now().Add(-10 * time.Hour) } -// Reset remove any bans from the backends and reset their states +// Reset reset all backend states func (cp *ConsensusPoller) Reset() { for _, be := range cp.backendGroup.Backends { cp.backendState[be] = &backendState{} } } -// fetchBlock Convenient wrapper to make a request to get a block directly from the backend +// fetchBlock is a convenient wrapper to make a request to get a block directly from the backend func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block string) (blockNumber hexutil.Uint64, blockHash string, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_getBlockByNumber", block, false) @@ -557,7 +561,7 @@ func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block st return } -// getPeerCount Convenient wrapper to retrieve the current peer count from the backend +// getPeerCount is a convenient wrapper to retrieve the current peer count from the backend func (cp *ConsensusPoller) getPeerCount(ctx context.Context, be *Backend) (count uint64, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "net_peerCount") diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 320139a..7d98732 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -80,6 +80,7 @@ func TestConsensus(t *testing.T) { node.handler.ResetOverrides() node.mockBackend.Reset() } + bg.Consensus.ClearListeners() bg.Consensus.Reset() } From 1b40d776a616f12fc5867b558308585cddc9edf0 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 26 May 2023 19:15:09 -0700 Subject: [PATCH 112/212] sane values for consensus_max_block_lag --- proxyd/proxyd/consensus_poller.go | 2 +- proxyd/proxyd/example.config.toml | 4 ++-- proxyd/proxyd/integration_tests/consensus_test.go | 12 ++++++------ .../proxyd/integration_tests/testdata/consensus.toml | 2 +- .../testdata/consensus_responses.yml | 11 +++++++++++ 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 5aff27e..57bdbb3 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -220,7 +220,7 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller banPeriod: 5 * time.Minute, maxUpdateThreshold: 30 * time.Second, - maxBlockLag: 50, + maxBlockLag: 8, // quarter of an epoch, 8*12 seconds = 96 seconds ~ 1.6 minutes minPeerCount: 3, } diff --git a/proxyd/proxyd/example.config.toml b/proxyd/proxyd/example.config.toml index 413cd61..19c6c26 100644 --- a/proxyd/proxyd/example.config.toml +++ b/proxyd/proxyd/example.config.toml @@ -93,8 +93,8 @@ backends = ["infura"] # consensus_ban_period = "1m" # Maximum delay for update the backend, default 30s # consensus_max_update_threshold = "20s" -# Maximum block lag, default 50 -# consensus_max_block_lag = 10 +# Maximum block lag, default 8 +# consensus_max_block_lag = 16 # Minimum peer count, default 3 # consensus_min_peer_count = 4 diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 7d98732..24398b0 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -175,12 +175,12 @@ func TestConsensus(t *testing.T) { t.Run("prevent using a backend lagging behind", func(t *testing.T) { reset() - // node2 is 51 blocks ahead of node1 (0x101 + 51 = 0x134) - overrideBlock("node2", "latest", "0x134") + // node2 is 8+1 blocks ahead of node1 (0x101 + 8+1 = 0x10a) + overrideBlock("node2", "latest", "0x10a") update() - // since we ignored node1, the consensus should be at 0x133 - require.Equal(t, "0x134", bg.Consensus.GetLatestBlockNumber().String()) + // since we ignored node1, the consensus should be at 0x10a + require.Equal(t, "0x10a", bg.Consensus.GetLatestBlockNumber().String()) require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) @@ -192,8 +192,8 @@ func TestConsensus(t *testing.T) { t.Run("prevent using a backend lagging behind - one before limit", func(t *testing.T) { reset() - // node2 is 50 blocks ahead of node1 (0x101 + 50 = 0x133) - overrideBlock("node2", "latest", "0x133") + // node2 is 8 blocks ahead of node1 (0x101 + 8 = 0x109) + overrideBlock("node2", "latest", "0x109") update() // both nodes are in consensus with the lowest block diff --git a/proxyd/proxyd/integration_tests/testdata/consensus.toml b/proxyd/proxyd/integration_tests/testdata/consensus.toml index 3f92a3d..03b11d0 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus.toml +++ b/proxyd/proxyd/integration_tests/testdata/consensus.toml @@ -18,7 +18,7 @@ consensus_aware = true consensus_handler = "noop" # allow more control over the consensus poller for tests consensus_ban_period = "1m" consensus_max_update_threshold = "2m" -consensus_max_block_lag = 50 +consensus_max_block_lag = 8 consensus_min_peer_count = 4 [rpc_method_mappings] diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml index ea8ef72..ee413c5 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml +++ b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml @@ -63,6 +63,17 @@ "number": "0x103" } } +- method: eth_getBlockByNumber + block: 0x10a + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x10a", + "number": "0x10a" + } + } - method: eth_getBlockByNumber block: 0x132 response: > From 0c27c83aba1ca6f8d0091381ee56079bbbb5ef40 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 26 May 2023 19:29:24 -0700 Subject: [PATCH 113/212] test refactor --- .../integration_tests/consensus_test.go | 92 +++++++++++-------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 24398b0..73f10f3 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -17,16 +17,15 @@ import ( ) type nodeContext struct { - mockBackend *MockBackend - backend *proxyd.Backend - handler *ms.MockedHandler + backend *proxyd.Backend // this is the actual backend impl in proxyd + mockBackend *MockBackend // this is the fake backend that we can use to mock responses + handler *ms.MockedHandler // this is where we control the state of mocked responses } -func TestConsensus(t *testing.T) { +func setup(t *testing.T) (map[string]nodeContext, *proxyd.BackendGroup, *ProxydHTTPClient, func()) { + // setup mock servers node1 := NewMockBackend(nil) - defer node1.Close() node2 := NewMockBackend(nil) - defer node2.Close() dir, err := os.Getwd() require.NoError(t, err) @@ -50,16 +49,19 @@ func TestConsensus(t *testing.T) { node1.SetHandler(http.HandlerFunc(h1.Handler)) node2.SetHandler(http.HandlerFunc(h2.Handler)) + // setup proxyd config := ReadConfig("consensus") - ctx := context.Background() svr, shutdown, err := proxyd.Start(config) require.NoError(t, err) - client := NewProxydClient("http://127.0.0.1:8545") - defer shutdown() + // expose the proxyd client + client := NewProxydClient("http://127.0.0.1:8545") + + // expose the backend group bg := svr.BackendGroups["node"] require.NotNil(t, bg) require.NotNil(t, bg.Consensus) + require.Equal(t, 2, len(bg.Backends)) // should match config // convenient mapping to access the nodes by name nodes := map[string]nodeContext{ @@ -75,14 +77,16 @@ func TestConsensus(t *testing.T) { }, } - reset := func() { - for _, node := range nodes { - node.handler.ResetOverrides() - node.mockBackend.Reset() - } - bg.Consensus.ClearListeners() - bg.Consensus.Reset() - } + return nodes, bg, client, shutdown +} + +func TestConsensus(t *testing.T) { + nodes, bg, client, shutdown := setup(t) + defer nodes["node1"].mockBackend.Close() + defer nodes["node2"].mockBackend.Close() + defer shutdown() + + ctx := context.Background() // poll for updated consensus update := func() { @@ -92,6 +96,16 @@ func TestConsensus(t *testing.T) { bg.Consensus.UpdateBackendGroupConsensus(ctx) } + // convenient methods to manipulate state and mock responses + reset := func() { + for _, node := range nodes { + node.handler.ResetOverrides() + node.mockBackend.Reset() + } + bg.Consensus.ClearListeners() + bg.Consensus.Reset() + } + override := func(node string, method string, block string, response string) { nodes[node].handler.AddOverride(&ms.MethodTemplate{ Method: method, @@ -132,6 +146,7 @@ func TestConsensus(t *testing.T) { })) } + // force ban node2 and make sure node1 is the only one in consensus useOnlyNode1 := func() { overridePeerCount("node2", 0) update() @@ -139,7 +154,7 @@ func TestConsensus(t *testing.T) { consensusGroup := bg.Consensus.GetConsensusGroup() require.Equal(t, 1, len(consensusGroup)) require.Contains(t, consensusGroup, nodes["node1"].backend) - node1.Reset() + nodes["node1"].mockBackend.Reset() } t.Run("initial consensus", func(t *testing.T) { @@ -534,11 +549,11 @@ func TestConsensus(t *testing.T) { require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) // reset request counts - node1.Reset() - node2.Reset() + nodes["node1"].mockBackend.Reset() + nodes["node2"].mockBackend.Reset() - require.Equal(t, 0, len(node1.Requests())) - require.Equal(t, 0, len(node2.Requests())) + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) + require.Equal(t, 0, len(nodes["node2"].mockBackend.Requests())) // there is a random component to this test, // since our round-robin implementation shuffles the ordering @@ -556,9 +571,10 @@ func TestConsensus(t *testing.T) { numberReqs-- } - msg := fmt.Sprintf("n1 %d, n2 %d", len(node1.Requests()), len(node2.Requests())) - require.GreaterOrEqual(t, len(node1.Requests()), 50, msg) - require.GreaterOrEqual(t, len(node2.Requests()), 50, msg) + msg := fmt.Sprintf("n1 %d, n2 %d", + len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) + require.GreaterOrEqual(t, len(nodes["node1"].mockBackend.Requests()), 50, msg) + require.GreaterOrEqual(t, len(nodes["node2"].mockBackend.Requests()), 50, msg) }) t.Run("load balancing should not hit if node is not healthy", func(t *testing.T) { @@ -566,11 +582,11 @@ func TestConsensus(t *testing.T) { useOnlyNode1() // reset request counts - node1.Reset() - node2.Reset() + nodes["node1"].mockBackend.Reset() + nodes["node2"].mockBackend.Reset() - require.Equal(t, 0, len(node1.Requests())) - require.Equal(t, 0, len(node2.Requests())) + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) numberReqs := 10 for numberReqs > 0 { @@ -580,16 +596,17 @@ func TestConsensus(t *testing.T) { numberReqs-- } - msg := fmt.Sprintf("n1 %d, n2 %d", len(node1.Requests()), len(node2.Requests())) - require.Equal(t, len(node1.Requests()), 10, msg) - require.Equal(t, len(node2.Requests()), 0, msg) + msg := fmt.Sprintf("n1 %d, n2 %d", + len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) + require.Equal(t, len(nodes["node1"].mockBackend.Requests()), 10, msg) + require.Equal(t, len(nodes["node2"].mockBackend.Requests()), 0, msg) }) t.Run("rewrite response of eth_blockNumber", func(t *testing.T) { reset() update() - totalRequests := len(node1.Requests()) + len(node2.Requests()) + totalRequests := len(nodes["node1"].mockBackend.Requests()) + len(nodes["node2"].mockBackend.Requests()) require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) resRaw, statusCode, err := client.SendRPC("eth_blockNumber", nil) @@ -602,7 +619,8 @@ func TestConsensus(t *testing.T) { require.Equal(t, "0x101", jsonMap["result"]) // no extra request hit the backends - require.Equal(t, totalRequests, len(node1.Requests())+len(node2.Requests())) + require.Equal(t, totalRequests, + len(nodes["node1"].mockBackend.Requests())+len(nodes["node2"].mockBackend.Requests())) }) t.Run("rewrite request of eth_getBlockByNumber for latest", func(t *testing.T) { @@ -614,7 +632,7 @@ func TestConsensus(t *testing.T) { require.Equal(t, 200, statusCode) var jsonMap map[string]interface{} - err = json.Unmarshal(node1.Requests()[0].Body, &jsonMap) + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) require.NoError(t, err) require.Equal(t, "0x101", jsonMap["params"].([]interface{})[0]) }) @@ -628,7 +646,7 @@ func TestConsensus(t *testing.T) { require.Equal(t, 200, statusCode) var jsonMap map[string]interface{} - err = json.Unmarshal(node1.Requests()[0].Body, &jsonMap) + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) require.NoError(t, err) require.Equal(t, "0xc1", jsonMap["params"].([]interface{})[0]) }) @@ -642,7 +660,7 @@ func TestConsensus(t *testing.T) { require.Equal(t, 200, statusCode) var jsonMap map[string]interface{} - err = json.Unmarshal(node1.Requests()[0].Body, &jsonMap) + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) require.NoError(t, err) require.Equal(t, "0xe1", jsonMap["params"].([]interface{})[0]) }) From 1f657b5f465793171ef429611a48674b7c270731 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 27 May 2023 02:02:39 -0700 Subject: [PATCH 114/212] ban backend if error rate is too high --- .../integration_tests/consensus_test.go | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 73f10f3..e8927d2 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -283,6 +283,34 @@ func TestConsensus(t *testing.T) { require.Equal(t, "0xe2", bg.Consensus.GetSafeBlockNumber().String()) }) + t.Run("ban backend if error rate is too high", func(t *testing.T) { + reset() + useOnlyNode1() + + // replace node1 handler with one that always returns 500 + oldHandler := nodes["node1"].mockBackend.handler + defer func() { nodes["node1"].mockBackend.handler = oldHandler }() + + nodes["node1"].mockBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(503) + })) + + numberReqs := 10 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) + require.NoError(t, err) + require.Equal(t, 503, statusCode) + numberReqs-- + } + + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + }) + t.Run("ban backend if tags are messed - safe < finalized", func(t *testing.T) { reset() overrideBlock("node1", "finalized", "0xb1") From 7405b8b360266147837a6013af347e714576782b Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 27 May 2023 04:14:29 -0700 Subject: [PATCH 115/212] refactor poller --- proxyd/proxyd/consensus_poller.go | 220 +++++++++++++++--------------- 1 file changed, 113 insertions(+), 107 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 57bdbb3..decacd3 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -34,8 +34,7 @@ type ConsensusPoller struct { tracker ConsensusTracker asyncHandler ConsensusAsyncHandler - minPeerCount uint64 - + minPeerCount uint64 banPeriod time.Duration maxUpdateThreshold time.Duration maxBlockLag uint64 @@ -220,7 +219,7 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller banPeriod: 5 * time.Minute, maxUpdateThreshold: 30 * time.Second, - maxBlockLag: 8, // quarter of an epoch, 8*12 seconds = 96 seconds ~ 1.6 minutes + maxBlockLag: 8, // 8*12 seconds = 96 seconds ~ 1.6 minutes minPeerCount: 3, } @@ -253,12 +252,11 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { // if backend is not healthy state we'll only resume checking it after ban if !be.IsHealthy() { - log.Warn("backend banned - not online or not healthy", "backend", be.Name) + log.Warn("backend banned - not healthy", "backend", be.Name) cp.Ban(be) return } - // if backend it not in sync we'll check again after ban inSync, err := cp.isInSync(ctx, be) RecordConsensusBackendInSync(be, err == nil && inSync) if err != nil { @@ -276,21 +274,27 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { latestBlockNumber, latestBlockHash, err := cp.fetchBlock(ctx, be, "latest") if err != nil { - log.Warn("error updating backend", "name", be.Name, "err", err) + log.Warn("error updating backend - latest block", "name", be.Name, "err", err) } safeBlockNumber, _, err := cp.fetchBlock(ctx, be, "safe") if err != nil { - log.Warn("error updating backend", "name", be.Name, "err", err) + log.Warn("error updating backend - safe block", "name", be.Name, "err", err) } finalizedBlockNumber, _, err := cp.fetchBlock(ctx, be, "finalized") if err != nil { - log.Warn("error updating backend", "name", be.Name, "err", err) + log.Warn("error updating backend - finalized block", "name", be.Name, "err", err) } - _, _, _, _, oldFinalized, oldSafe, _, _ := cp.getBackendState(be) - expectedBlockTags := cp.checkExpectedBlockTags(finalizedBlockNumber, oldFinalized, safeBlockNumber, oldSafe, latestBlockNumber) + bs := cp.getBackendState(be) + oldFinalized := bs.finalizedBlockNumber + oldSafe := bs.safeBlockNumber + + expectedBlockTags := cp.checkExpectedBlockTags( + finalizedBlockNumber, oldFinalized, + safeBlockNumber, oldSafe, + latestBlockNumber) changed, updateDelay := cp.setBackendState(be, peerCount, inSync, latestBlockNumber, latestBlockHash, @@ -342,116 +346,108 @@ func (cp *ConsensusPoller) checkExpectedBlockTags(currentFinalized hexutil.Uint6 // UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - var highestLatestBlock hexutil.Uint64 - - var lowestLatestBlock hexutil.Uint64 - var lowestLatestBlockHash string - - var lowestFinalizedBlock hexutil.Uint64 - var lowestSafeBlock hexutil.Uint64 - + // get the latest block number from the tracker currentConsensusBlockNumber := cp.GetLatestBlockNumber() - // find the highest block, in order to use it defining the highest non-lagging ancestor block + // find out what backends are the candidates to be in the consensus group + // and create a copy of current their state + // + // a serving node needs to be: + // - not banned + // - healthy (network latency and error rate) + // - with minimum peer count + // - in sync + // - updated recently + // - not lagging latest block + + candidates := make(map[*Backend]*backendState, len(cp.backendGroup.Backends)) + filteredBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) for _, be := range cp.backendGroup.Backends { - peerCount, inSync, backendLatestBlockNumber, _, _, _, lastUpdate, _ := cp.getBackendState(be) + bs := cp.getBackendState(be) + passed := true + if time.Now().Before(bs.bannedUntil) { + passed = false + } + if !be.IsHealthy() { + passed = false + } + if !be.skipPeerCountCheck && bs.peerCount < cp.minPeerCount { + passed = false + } + if !bs.inSync { + passed = false + } + if bs.lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { + passed = false + } + if passed { + candidates[be] = bs + } else { + filteredBackendsNames = append(filteredBackendsNames, be.Name) + } + } - if cp.IsBanned(be) { - continue - } - if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { - continue - } - if !inSync { - continue - } - if lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { - continue - } - - if backendLatestBlockNumber > highestLatestBlock { - highestLatestBlock = backendLatestBlockNumber + // find the highest block, in order to use it defining the highest non-lagging ancestor block + var highestLatestBlock hexutil.Uint64 + for _, bs := range candidates { + if bs.latestBlockNumber > highestLatestBlock { + highestLatestBlock = bs.latestBlockNumber } } // find the highest common ancestor block - for _, be := range cp.backendGroup.Backends { - peerCount, inSync, backendLatestBlockNumber, backendLatestBlockHash, backendFinalizedBlockNumber, backendSafeBlockNumber, lastUpdate, _ := cp.getBackendState(be) - - if cp.IsBanned(be) { - continue - } - if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { - continue - } - if !inSync { - continue - } - if lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { - continue - } - + var lowestLatestBlock hexutil.Uint64 + var lowestLatestBlockHash string + var lowestFinalizedBlock hexutil.Uint64 + var lowestSafeBlock hexutil.Uint64 + lagging := make([]*Backend, 0, len(candidates)) + for be, bs := range candidates { // check if backend is lagging behind the highest block - if backendLatestBlockNumber < highestLatestBlock && uint64(highestLatestBlock-backendLatestBlockNumber) > cp.maxBlockLag { + if bs.latestBlockNumber < highestLatestBlock && uint64(highestLatestBlock-bs.latestBlockNumber) > cp.maxBlockLag { + lagging = append(lagging, be) continue } - if lowestLatestBlock == 0 || backendLatestBlockNumber < lowestLatestBlock { - lowestLatestBlock = backendLatestBlockNumber - lowestLatestBlockHash = backendLatestBlockHash + // update the lowest common ancestor block + if lowestLatestBlock == 0 || bs.latestBlockNumber < lowestLatestBlock { + lowestLatestBlock = bs.latestBlockNumber + lowestLatestBlockHash = bs.latestBlockHash } - if lowestFinalizedBlock == 0 || backendFinalizedBlockNumber < lowestFinalizedBlock { - lowestFinalizedBlock = backendFinalizedBlockNumber + // update the lowest finalized block + if lowestFinalizedBlock == 0 || bs.finalizedBlockNumber < lowestFinalizedBlock { + lowestFinalizedBlock = bs.finalizedBlockNumber } - if lowestSafeBlock == 0 || backendSafeBlockNumber < lowestSafeBlock { - lowestSafeBlock = backendSafeBlockNumber + // update the lowest safe block + if lowestSafeBlock == 0 || bs.safeBlockNumber < lowestSafeBlock { + lowestSafeBlock = bs.safeBlockNumber } } + // remove lagging backends from the candidates + for _, be := range lagging { + filteredBackendsNames = append(filteredBackendsNames, be.Name) + delete(candidates, be) + } + + // find the proposed block among the candidates proposedBlock := lowestLatestBlock proposedBlockHash := lowestLatestBlockHash hasConsensus := false - // check if everybody agrees on the same block hash - consensusBackends := make([]*Backend, 0, len(cp.backendGroup.Backends)) - consensusBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) - filteredBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) - if lowestLatestBlock > currentConsensusBlockNumber { log.Debug("validating consensus on block", "lowestLatestBlock", lowestLatestBlock) } // if there is no block to propose, the consensus is automatically broken + // this can happen when backends have just recovered broken := proposedBlock == 0 && currentConsensusBlockNumber > 0 if proposedBlock > 0 { for !hasConsensus { allAgreed := true - consensusBackends = consensusBackends[:0] - filteredBackendsNames = filteredBackendsNames[:0] - for _, be := range cp.backendGroup.Backends { - /* - a serving node needs to be: - - healthy (network) - - updated recently - - not banned - - with minimum peer count - - not lagging latest block - - in sync - */ - - peerCount, inSync, latestBlockNumber, _, _, _, lastUpdate, bannedUntil := cp.getBackendState(be) - notUpdated := lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) - isBanned := time.Now().Before(bannedUntil) - notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount - lagging := latestBlockNumber < proposedBlock - if !be.IsHealthy() || notUpdated || isBanned || notEnoughPeers || lagging || !inSync { - filteredBackendsNames = append(filteredBackendsNames, be.Name) - continue - } - + for be, _ := range candidates { actualBlockNumber, actualBlockHash, err := cp.fetchBlock(ctx, be, proposedBlock.String()) if err != nil { log.Warn("error updating backend", "name", be.Name, "err", err) @@ -469,8 +465,6 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { allAgreed = false break } - consensusBackends = append(consensusBackends, be) - consensusBackendsNames = append(consensusBackendsNames, be.Name) } if allAgreed { hasConsensus = true @@ -488,26 +482,39 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { for _, l := range cp.listeners { l() } - log.Info("consensus broken", "currentConsensusBlockNumber", currentConsensusBlockNumber, "proposedBlock", proposedBlock, "proposedBlockHash", proposedBlockHash) + log.Info("consensus broken", + "currentConsensusBlockNumber", currentConsensusBlockNumber, + "proposedBlock", proposedBlock, + "proposedBlockHash", proposedBlockHash) } cp.tracker.SetLatestBlockNumber(proposedBlock) cp.tracker.SetSafeBlockNumber(lowestSafeBlock) cp.tracker.SetFinalizedBlockNumber(lowestFinalizedBlock) + // update consensus group + group := make([]*Backend, 0, len(candidates)) + consensusBackendsNames := make([]string, 0, len(candidates)) + for be, _ := range candidates { + group = append(group, be) + consensusBackendsNames = append(consensusBackendsNames, be.Name) + } cp.consensusGroupMux.Lock() - cp.consensusGroup = consensusBackends + cp.consensusGroup = group cp.consensusGroupMux.Unlock() RecordGroupConsensusLatestBlock(cp.backendGroup, proposedBlock) RecordGroupConsensusSafeBlock(cp.backendGroup, lowestSafeBlock) RecordGroupConsensusFinalizedBlock(cp.backendGroup, lowestFinalizedBlock) - RecordGroupConsensusCount(cp.backendGroup, len(consensusBackends)) + RecordGroupConsensusCount(cp.backendGroup, len(group)) RecordGroupConsensusFilteredCount(cp.backendGroup, len(filteredBackendsNames)) RecordGroupTotalCount(cp.backendGroup, len(cp.backendGroup.Backends)) - log.Debug("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) + log.Debug("group state", + "proposedBlock", proposedBlock, + "consensusBackends", strings.Join(consensusBackendsNames, ", "), + "filteredBackends", strings.Join(filteredBackendsNames, ", ")) } // IsBanned checks if a specific backend is banned @@ -606,23 +613,22 @@ func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bo return res, nil } -func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, inSync bool, - latestBlockNumber hexutil.Uint64, latestBlockHash string, - finalizedBlockNumber hexutil.Uint64, - safeBlockNumber hexutil.Uint64, - lastUpdate time.Time, bannedUntil time.Time) { +func (cp *ConsensusPoller) getBackendState(be *Backend) *backendState { bs := cp.backendState[be] defer bs.backendStateMux.Unlock() bs.backendStateMux.Lock() - peerCount = bs.peerCount - inSync = bs.inSync - latestBlockNumber = bs.latestBlockNumber - latestBlockHash = bs.latestBlockHash - finalizedBlockNumber = bs.finalizedBlockNumber - safeBlockNumber = bs.safeBlockNumber - lastUpdate = bs.lastUpdate - bannedUntil = bs.bannedUntil - return + + // we return a copy so that the caller can use it without locking + return &backendState{ + latestBlockNumber: bs.latestBlockNumber, + latestBlockHash: bs.latestBlockHash, + safeBlockNumber: bs.safeBlockNumber, + finalizedBlockNumber: bs.finalizedBlockNumber, + peerCount: bs.peerCount, + inSync: bs.inSync, + lastUpdate: bs.lastUpdate, + bannedUntil: bs.bannedUntil, + } } func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync bool, From 8436728419cf868ec291fdd29bf008c11419d538 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 27 May 2023 04:31:05 -0700 Subject: [PATCH 116/212] extract candidates logic, refactor update backend --- proxyd/proxyd/consensus_poller.go | 225 ++++++++++-------- .../integration_tests/consensus_test.go | 2 +- 2 files changed, 124 insertions(+), 103 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index decacd3..9ed28a4 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -57,6 +57,10 @@ type backendState struct { bannedUntil time.Time } +func (bs backendState) IsBanned() bool { + return time.Now().Before(bs.bannedUntil) +} + // GetConsensusGroup returns the backend members that are agreeing in a consensus func (cp *ConsensusPoller) GetConsensusGroup() []*Backend { defer cp.consensusGroupMux.Unlock() @@ -242,10 +246,10 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller // UpdateBackend refreshes the consensus state of a single backend func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { - banned := cp.IsBanned(be) - RecordConsensusBackendBanned(be, banned) + bs := cp.getBackendState(be) + RecordConsensusBackendBanned(be, bs.IsBanned()) - if banned { + if bs.IsBanned() { log.Debug("skipping backend - banned", "backend", be.Name) return } @@ -287,36 +291,19 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { log.Warn("error updating backend - finalized block", "name", be.Name, "err", err) } - bs := cp.getBackendState(be) oldFinalized := bs.finalizedBlockNumber oldSafe := bs.safeBlockNumber - expectedBlockTags := cp.checkExpectedBlockTags( - finalizedBlockNumber, oldFinalized, - safeBlockNumber, oldSafe, - latestBlockNumber) + updateDelay := time.Since(bs.lastUpdate) + RecordConsensusBackendUpdateDelay(be, updateDelay) - changed, updateDelay := cp.setBackendState(be, peerCount, inSync, + changed := cp.setBackendState(be, peerCount, inSync, latestBlockNumber, latestBlockHash, finalizedBlockNumber, safeBlockNumber) RecordBackendLatestBlock(be, latestBlockNumber) RecordBackendSafeBlock(be, safeBlockNumber) RecordBackendFinalizedBlock(be, finalizedBlockNumber) - RecordBackendUnexpectedBlockTags(be, !expectedBlockTags) - RecordConsensusBackendUpdateDelay(be, updateDelay) - - if !expectedBlockTags { - log.Warn("backend banned - unexpected block tags", - "backend", be.Name, - "oldFinalized", oldFinalized, - "finalizedBlockNumber", finalizedBlockNumber, - "oldSafe", oldSafe, - "safeBlockNumber", safeBlockNumber, - "latestBlockNumber", latestBlockNumber, - ) - cp.Ban(be) - } if changed { log.Debug("backend state updated", @@ -329,6 +316,26 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { "safeBlockNumber", safeBlockNumber, "updateDelay", updateDelay) } + + // sanity check for latest, safe and finalized block tags + expectedBlockTags := cp.checkExpectedBlockTags( + finalizedBlockNumber, oldFinalized, + safeBlockNumber, oldSafe, + latestBlockNumber) + + RecordBackendUnexpectedBlockTags(be, !expectedBlockTags) + + if !expectedBlockTags { + log.Warn("backend banned - unexpected block tags", + "backend", be.Name, + "oldFinalized", oldFinalized, + "finalizedBlockNumber", finalizedBlockNumber, + "oldSafe", oldSafe, + "safeBlockNumber", safeBlockNumber, + "latestBlockNumber", latestBlockNumber, + ) + cp.Ban(be) + } } // checkExpectedBlockTags for unexpected conditions on block tags @@ -349,101 +356,41 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { // get the latest block number from the tracker currentConsensusBlockNumber := cp.GetLatestBlockNumber() - // find out what backends are the candidates to be in the consensus group - // and create a copy of current their state - // - // a serving node needs to be: - // - not banned - // - healthy (network latency and error rate) - // - with minimum peer count - // - in sync - // - updated recently - // - not lagging latest block + // get the candidates for the consensus group + candidates := cp.getConsensusCandidates() - candidates := make(map[*Backend]*backendState, len(cp.backendGroup.Backends)) - filteredBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) - for _, be := range cp.backendGroup.Backends { - bs := cp.getBackendState(be) - passed := true - if time.Now().Before(bs.bannedUntil) { - passed = false - } - if !be.IsHealthy() { - passed = false - } - if !be.skipPeerCountCheck && bs.peerCount < cp.minPeerCount { - passed = false - } - if !bs.inSync { - passed = false - } - if bs.lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { - passed = false - } - if passed { - candidates[be] = bs - } else { - filteredBackendsNames = append(filteredBackendsNames, be.Name) - } - } - - // find the highest block, in order to use it defining the highest non-lagging ancestor block - var highestLatestBlock hexutil.Uint64 - for _, bs := range candidates { - if bs.latestBlockNumber > highestLatestBlock { - highestLatestBlock = bs.latestBlockNumber - } - } - - // find the highest common ancestor block + // update the lowest latest block number and hash + // the lowest safe block number + // the lowest finalized block number var lowestLatestBlock hexutil.Uint64 var lowestLatestBlockHash string var lowestFinalizedBlock hexutil.Uint64 var lowestSafeBlock hexutil.Uint64 - lagging := make([]*Backend, 0, len(candidates)) - for be, bs := range candidates { - // check if backend is lagging behind the highest block - if bs.latestBlockNumber < highestLatestBlock && uint64(highestLatestBlock-bs.latestBlockNumber) > cp.maxBlockLag { - lagging = append(lagging, be) - continue - } - - // update the lowest common ancestor block + for _, bs := range candidates { if lowestLatestBlock == 0 || bs.latestBlockNumber < lowestLatestBlock { lowestLatestBlock = bs.latestBlockNumber lowestLatestBlockHash = bs.latestBlockHash } - - // update the lowest finalized block if lowestFinalizedBlock == 0 || bs.finalizedBlockNumber < lowestFinalizedBlock { lowestFinalizedBlock = bs.finalizedBlockNumber } - - // update the lowest safe block if lowestSafeBlock == 0 || bs.safeBlockNumber < lowestSafeBlock { lowestSafeBlock = bs.safeBlockNumber } } - // remove lagging backends from the candidates - for _, be := range lagging { - filteredBackendsNames = append(filteredBackendsNames, be.Name) - delete(candidates, be) - } - // find the proposed block among the candidates + // the proposed block needs have the same hash in the entire consensus group proposedBlock := lowestLatestBlock proposedBlockHash := lowestLatestBlockHash hasConsensus := false + broken := false if lowestLatestBlock > currentConsensusBlockNumber { log.Debug("validating consensus on block", "lowestLatestBlock", lowestLatestBlock) } - // if there is no block to propose, the consensus is automatically broken - // this can happen when backends have just recovered - broken := proposedBlock == 0 && currentConsensusBlockNumber > 0 - + // if there is a block to propose, check if it is the same in all backends if proposedBlock > 0 { for !hasConsensus { allAgreed := true @@ -459,7 +406,12 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { blocksDontMatch := (actualBlockNumber != proposedBlock) || (actualBlockHash != proposedBlockHash) if blocksDontMatch { if currentConsensusBlockNumber >= actualBlockNumber { - log.Warn("backend broke consensus", "name", be.Name, "blockNum", actualBlockNumber, "proposedBlockNum", proposedBlock, "blockHash", actualBlockHash, "proposedBlockHash", proposedBlockHash) + log.Warn("backend broke consensus", + "name", be.Name, + "actualBlockNumber", actualBlockNumber, + "actualBlockHash", actualBlockHash, + "proposedBlock", proposedBlock, + "proposedBlockHash", proposedBlockHash) broken = true } allAgreed = false @@ -488,6 +440,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { "proposedBlockHash", proposedBlockHash) } + // update tracker cp.tracker.SetLatestBlockNumber(proposedBlock) cp.tracker.SetSafeBlockNumber(lowestSafeBlock) cp.tracker.SetFinalizedBlockNumber(lowestFinalizedBlock) @@ -495,10 +448,17 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { // update consensus group group := make([]*Backend, 0, len(candidates)) consensusBackendsNames := make([]string, 0, len(candidates)) - for be, _ := range candidates { - group = append(group, be) - consensusBackendsNames = append(consensusBackendsNames, be.Name) + filteredBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) + for _, be := range cp.backendGroup.Backends { + _, exist := candidates[be] + if exist { + group = append(group, be) + consensusBackendsNames = append(consensusBackendsNames, be.Name) + } else { + filteredBackendsNames = append(filteredBackendsNames, be.Name) + } } + cp.consensusGroupMux.Lock() cp.consensusGroup = group cp.consensusGroupMux.Unlock() @@ -522,7 +482,7 @@ func (cp *ConsensusPoller) IsBanned(be *Backend) bool { bs := cp.backendState[be] defer bs.backendStateMux.Unlock() bs.backendStateMux.Lock() - return time.Now().Before(bs.bannedUntil) + return bs.IsBanned() } // Ban bans a specific backend @@ -531,6 +491,9 @@ func (cp *ConsensusPoller) Ban(be *Backend) { defer bs.backendStateMux.Unlock() bs.backendStateMux.Lock() bs.bannedUntil = time.Now().Add(cp.banPeriod) + + // when we ban a node, we give it the chance to start from any block when it is back + bs.latestBlockNumber = 0 bs.safeBlockNumber = 0 bs.finalizedBlockNumber = 0 } @@ -613,12 +576,12 @@ func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bo return res, nil } +// getBackendState creates a copy of backend state so that the caller can use it without locking func (cp *ConsensusPoller) getBackendState(be *Backend) *backendState { bs := cp.backendState[be] defer bs.backendStateMux.Unlock() bs.backendStateMux.Lock() - // we return a copy so that the caller can use it without locking return &backendState{ latestBlockNumber: bs.latestBlockNumber, latestBlockHash: bs.latestBlockHash, @@ -634,18 +597,76 @@ func (cp *ConsensusPoller) getBackendState(be *Backend) *backendState { func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync bool, latestBlockNumber hexutil.Uint64, latestBlockHash string, finalizedBlockNumber hexutil.Uint64, - safeBlockNumber hexutil.Uint64) (changed bool, updateDelay time.Duration) { + safeBlockNumber hexutil.Uint64) bool { bs := cp.backendState[be] bs.backendStateMux.Lock() - changed = bs.latestBlockHash != latestBlockHash + changed := bs.latestBlockHash != latestBlockHash bs.peerCount = peerCount bs.inSync = inSync bs.latestBlockNumber = latestBlockNumber bs.latestBlockHash = latestBlockHash bs.finalizedBlockNumber = finalizedBlockNumber bs.safeBlockNumber = safeBlockNumber - updateDelay = time.Since(bs.lastUpdate) bs.lastUpdate = time.Now() bs.backendStateMux.Unlock() - return + return changed +} + +// getConsensusCandidates find out what backends are the candidates to be in the consensus group +// and create a copy of current their state +// +// a candidate is a serving node within the following conditions: +// - not banned +// - healthy (network latency and error rate) +// - with minimum peer count +// - in sync +// - updated recently +// - not lagging latest block +func (cp *ConsensusPoller) getConsensusCandidates() map[*Backend]*backendState { + candidates := make(map[*Backend]*backendState, len(cp.backendGroup.Backends)) + + for _, be := range cp.backendGroup.Backends { + bs := cp.getBackendState(be) + if bs.IsBanned() { + continue + } + if !be.IsHealthy() { + continue + } + if !be.skipPeerCountCheck && bs.peerCount < cp.minPeerCount { + continue + } + if !bs.inSync { + continue + } + if bs.lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { + continue + } + + candidates[be] = bs + } + + // find the highest block, in order to use it defining the highest non-lagging ancestor block + var highestLatestBlock hexutil.Uint64 + for _, bs := range candidates { + if bs.latestBlockNumber > highestLatestBlock { + highestLatestBlock = bs.latestBlockNumber + } + } + + // find the highest common ancestor block + lagging := make([]*Backend, 0, len(candidates)) + for be, bs := range candidates { + // check if backend is lagging behind the highest block + if bs.latestBlockNumber < highestLatestBlock && uint64(highestLatestBlock-bs.latestBlockNumber) > cp.maxBlockLag { + lagging = append(lagging, be) + } + } + + // remove lagging backends from the candidates + for _, be := range lagging { + delete(candidates, be) + } + + return candidates } diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index e8927d2..98196c9 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -428,7 +428,7 @@ func TestConsensus(t *testing.T) { require.Equal(t, "0xb1", bg.Consensus.GetSafeBlockNumber().String()) }) - t.Run("latest dropped below safe, and stayed inconsistent after ban", func(t *testing.T) { + t.Run("latest dropped below safe, and stayed inconsistent", func(t *testing.T) { reset() useOnlyNode1() overrideBlock("node1", "latest", "0xd1") From dbfb39a4fa5a435ddf0fc7befcf146b72c22958f Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 27 May 2023 07:38:19 -0700 Subject: [PATCH 117/212] lint --- proxyd/proxyd/consensus_poller.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 9ed28a4..07edc31 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -57,7 +57,7 @@ type backendState struct { bannedUntil time.Time } -func (bs backendState) IsBanned() bool { +func (bs *backendState) IsBanned() bool { return time.Now().Before(bs.bannedUntil) } @@ -394,7 +394,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { if proposedBlock > 0 { for !hasConsensus { allAgreed := true - for be, _ := range candidates { + for be := range candidates { actualBlockNumber, actualBlockHash, err := cp.fetchBlock(ctx, be, proposedBlock.String()) if err != nil { log.Warn("error updating backend", "name", be.Name, "err", err) From 04058779a95f8f2b4c7857b9d521edd8c6f730b0 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 27 May 2023 10:18:34 -0700 Subject: [PATCH 118/212] small fixes --- proxyd/proxyd/consensus_poller.go | 12 +++++------- proxyd/proxyd/metrics.go | 7 ++++++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 07edc31..9e1c5eb 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -212,9 +212,6 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller ctx, cancelFunc := context.WithCancel(context.Background()) state := make(map[*Backend]*backendState, len(bg.Backends)) - for _, be := range bg.Backends { - state[be] = &backendState{} - } cp := &ConsensusPoller{ cancelFunc: cancelFunc, @@ -239,6 +236,7 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller cp.asyncHandler = NewPollerAsyncHandler(ctx, cp) } + cp.Reset() cp.asyncHandler.Init() return cp @@ -291,11 +289,11 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { log.Warn("error updating backend - finalized block", "name", be.Name, "err", err) } + // just for readability oldFinalized := bs.finalizedBlockNumber oldSafe := bs.safeBlockNumber - updateDelay := time.Since(bs.lastUpdate) - RecordConsensusBackendUpdateDelay(be, updateDelay) + RecordConsensusBackendUpdateDelay(be, bs.lastUpdate) changed := cp.setBackendState(be, peerCount, inSync, latestBlockNumber, latestBlockHash, @@ -314,7 +312,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { "latestBlockHash", latestBlockHash, "finalizedBlockNumber", finalizedBlockNumber, "safeBlockNumber", safeBlockNumber, - "updateDelay", updateDelay) + "lastUpdate", bs.lastUpdate) } // sanity check for latest, safe and finalized block tags @@ -658,7 +656,7 @@ func (cp *ConsensusPoller) getConsensusCandidates() map[*Backend]*backendState { lagging := make([]*Backend, 0, len(candidates)) for be, bs := range candidates { // check if backend is lagging behind the highest block - if bs.latestBlockNumber < highestLatestBlock && uint64(highestLatestBlock-bs.latestBlockNumber) > cp.maxBlockLag { + if uint64(highestLatestBlock-bs.latestBlockNumber) > cp.maxBlockLag { lagging = append(lagging, be) } } diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 3d8bfac..1d9602c 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -482,7 +482,12 @@ func RecordConsensusBackendInSync(b *Backend, inSync bool) { consensusInSyncBackend.WithLabelValues(b.Name).Set(boolToFloat64(inSync)) } -func RecordConsensusBackendUpdateDelay(b *Backend, delay time.Duration) { +func RecordConsensusBackendUpdateDelay(b *Backend, lastUpdate time.Time) { + // avoid recording the delay for the first update + if lastUpdate.IsZero() { + return + } + delay := time.Since(lastUpdate) consensusUpdateDelayBackend.WithLabelValues(b.Name).Set(float64(delay.Milliseconds())) } From 2bf2329dd3034decc619352f61342063c776d612 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 27 May 2023 10:30:50 -0700 Subject: [PATCH 119/212] order params chronologically: latest, safe, finalized --- proxyd/proxyd/backend.go | 2 +- proxyd/proxyd/consensus_poller.go | 46 ++++++++--------- proxyd/proxyd/consensus_tracker.go | 49 ++++++++++--------- .../integration_tests/consensus_test.go | 14 +++--- 4 files changed, 54 insertions(+), 57 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 6ba7a10..07c9fa2 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -564,8 +564,8 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch // We also rewrite block tags to enforce compliance with consensus rctx := RewriteContext{ latest: bg.Consensus.GetLatestBlockNumber(), - finalized: bg.Consensus.GetFinalizedBlockNumber(), safe: bg.Consensus.GetSafeBlockNumber(), + finalized: bg.Consensus.GetFinalizedBlockNumber(), } for i, req := range rpcReqs { diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 9e1c5eb..5944fa7 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -43,11 +43,10 @@ type ConsensusPoller struct { type backendState struct { backendStateMux sync.Mutex - latestBlockNumber hexutil.Uint64 - latestBlockHash string - - finalizedBlockNumber hexutil.Uint64 + latestBlockNumber hexutil.Uint64 + latestBlockHash string safeBlockNumber hexutil.Uint64 + finalizedBlockNumber hexutil.Uint64 peerCount uint64 inSync bool @@ -77,16 +76,16 @@ func (ct *ConsensusPoller) GetLatestBlockNumber() hexutil.Uint64 { return ct.tracker.GetLatestBlockNumber() } -// GetFinalizedBlockNumber returns the `finalized` agreed block number in a consensus -func (ct *ConsensusPoller) GetFinalizedBlockNumber() hexutil.Uint64 { - return ct.tracker.GetFinalizedBlockNumber() -} - // GetSafeBlockNumber returns the `safe` agreed block number in a consensus func (ct *ConsensusPoller) GetSafeBlockNumber() hexutil.Uint64 { return ct.tracker.GetSafeBlockNumber() } +// GetFinalizedBlockNumber returns the `finalized` agreed block number in a consensus +func (ct *ConsensusPoller) GetFinalizedBlockNumber() hexutil.Uint64 { + return ct.tracker.GetFinalizedBlockNumber() +} + func (cp *ConsensusPoller) Shutdown() { cp.asyncHandler.Shutdown() } @@ -289,15 +288,11 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { log.Warn("error updating backend - finalized block", "name", be.Name, "err", err) } - // just for readability - oldFinalized := bs.finalizedBlockNumber - oldSafe := bs.safeBlockNumber - RecordConsensusBackendUpdateDelay(be, bs.lastUpdate) changed := cp.setBackendState(be, peerCount, inSync, latestBlockNumber, latestBlockHash, - finalizedBlockNumber, safeBlockNumber) + safeBlockNumber, finalizedBlockNumber) RecordBackendLatestBlock(be, latestBlockNumber) RecordBackendSafeBlock(be, safeBlockNumber) @@ -310,25 +305,25 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { "inSync", inSync, "latestBlockNumber", latestBlockNumber, "latestBlockHash", latestBlockHash, - "finalizedBlockNumber", finalizedBlockNumber, "safeBlockNumber", safeBlockNumber, + "finalizedBlockNumber", finalizedBlockNumber, "lastUpdate", bs.lastUpdate) } // sanity check for latest, safe and finalized block tags expectedBlockTags := cp.checkExpectedBlockTags( - finalizedBlockNumber, oldFinalized, - safeBlockNumber, oldSafe, - latestBlockNumber) + latestBlockNumber, + bs.safeBlockNumber, safeBlockNumber, + bs.finalizedBlockNumber, finalizedBlockNumber) RecordBackendUnexpectedBlockTags(be, !expectedBlockTags) if !expectedBlockTags { log.Warn("backend banned - unexpected block tags", "backend", be.Name, - "oldFinalized", oldFinalized, + "oldFinalized", bs.finalizedBlockNumber, "finalizedBlockNumber", finalizedBlockNumber, - "oldSafe", oldSafe, + "oldSafe", bs.safeBlockNumber, "safeBlockNumber", safeBlockNumber, "latestBlockNumber", latestBlockNumber, ) @@ -340,9 +335,10 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { // - finalized block number should never decrease // - safe block number should never decrease // - finalized block should be <= safe block <= latest block -func (cp *ConsensusPoller) checkExpectedBlockTags(currentFinalized hexutil.Uint64, oldFinalized hexutil.Uint64, - currentSafe hexutil.Uint64, oldSafe hexutil.Uint64, - currentLatest hexutil.Uint64) bool { +func (cp *ConsensusPoller) checkExpectedBlockTags( + currentLatest hexutil.Uint64, + oldSafe hexutil.Uint64, currentSafe hexutil.Uint64, + oldFinalized hexutil.Uint64, currentFinalized hexutil.Uint64) bool { return currentFinalized >= oldFinalized && currentSafe >= oldSafe && currentFinalized <= currentSafe && @@ -594,8 +590,8 @@ func (cp *ConsensusPoller) getBackendState(be *Backend) *backendState { func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync bool, latestBlockNumber hexutil.Uint64, latestBlockHash string, - finalizedBlockNumber hexutil.Uint64, - safeBlockNumber hexutil.Uint64) bool { + safeBlockNumber hexutil.Uint64, + finalizedBlockNumber hexutil.Uint64) bool { bs := cp.backendState[be] bs.backendStateMux.Lock() changed := bs.latestBlockHash != latestBlockHash diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go index 83d2ec7..b0bcb45 100644 --- a/proxyd/proxyd/consensus_tracker.go +++ b/proxyd/proxyd/consensus_tracker.go @@ -15,17 +15,17 @@ import ( type ConsensusTracker interface { GetLatestBlockNumber() hexutil.Uint64 SetLatestBlockNumber(blockNumber hexutil.Uint64) - GetFinalizedBlockNumber() hexutil.Uint64 - SetFinalizedBlockNumber(blockNumber hexutil.Uint64) GetSafeBlockNumber() hexutil.Uint64 SetSafeBlockNumber(blockNumber hexutil.Uint64) + GetFinalizedBlockNumber() hexutil.Uint64 + SetFinalizedBlockNumber(blockNumber hexutil.Uint64) } // InMemoryConsensusTracker store and retrieve in memory, async-safe type InMemoryConsensusTracker struct { latestBlockNumber hexutil.Uint64 - finalizedBlockNumber hexutil.Uint64 safeBlockNumber hexutil.Uint64 + finalizedBlockNumber hexutil.Uint64 mutex sync.Mutex } @@ -49,20 +49,6 @@ func (ct *InMemoryConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uin ct.latestBlockNumber = blockNumber } -func (ct *InMemoryConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { - defer ct.mutex.Unlock() - ct.mutex.Lock() - - return ct.finalizedBlockNumber -} - -func (ct *InMemoryConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { - defer ct.mutex.Unlock() - ct.mutex.Lock() - - ct.finalizedBlockNumber = blockNumber -} - func (ct *InMemoryConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { defer ct.mutex.Unlock() ct.mutex.Lock() @@ -77,6 +63,20 @@ func (ct *InMemoryConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint6 ct.safeBlockNumber = blockNumber } +func (ct *InMemoryConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + return ct.finalizedBlockNumber +} + +func (ct *InMemoryConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + ct.finalizedBlockNumber = blockNumber +} + // RedisConsensusTracker uses a Redis `client` to store and retrieve consensus, async-safe type RedisConsensusTracker struct { ctx context.Context @@ -104,13 +104,6 @@ func (ct *RedisConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uint64 ct.client.Set(ct.ctx, ct.key("latest"), blockNumber, 0) } -func (ct *RedisConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { - return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key("finalized")).Val())) -} - -func (ct *RedisConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { - ct.client.Set(ct.ctx, ct.key("finalized"), blockNumber, 0) -} func (ct *RedisConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key("safe")).Val())) } @@ -118,3 +111,11 @@ func (ct *RedisConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { func (ct *RedisConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint64) { ct.client.Set(ct.ctx, ct.key("safe"), blockNumber, 0) } + +func (ct *RedisConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { + return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key("finalized")).Val())) +} + +func (ct *RedisConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { + ct.client.Set(ct.ctx, ct.key("finalized"), blockNumber, 0) +} diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 98196c9..8075637 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -267,8 +267,8 @@ func TestConsensus(t *testing.T) { overrideBlock("node2", "safe", "0xe2") update() - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) }) t.Run("advance safe and finalized", func(t *testing.T) { @@ -279,8 +279,8 @@ func TestConsensus(t *testing.T) { overrideBlock("node2", "safe", "0xe2") update() - require.Equal(t, "0xc2", bg.Consensus.GetFinalizedBlockNumber().String()) require.Equal(t, "0xe2", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc2", bg.Consensus.GetFinalizedBlockNumber().String()) }) t.Run("ban backend if error rate is too high", func(t *testing.T) { @@ -317,8 +317,8 @@ func TestConsensus(t *testing.T) { overrideBlock("node1", "safe", "0xa1") update() - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) consensusGroup := bg.Consensus.GetConsensusGroup() require.NotContains(t, consensusGroup, nodes["node1"].backend) @@ -349,8 +349,8 @@ func TestConsensus(t *testing.T) { update() require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) consensusGroup := bg.Consensus.GetConsensusGroup() require.NotContains(t, consensusGroup, nodes["node1"].backend) @@ -365,8 +365,8 @@ func TestConsensus(t *testing.T) { update() require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) consensusGroup := bg.Consensus.GetConsensusGroup() require.NotContains(t, consensusGroup, nodes["node1"].backend) @@ -397,8 +397,8 @@ func TestConsensus(t *testing.T) { require.Equal(t, 1, len(consensusGroup)) require.Equal(t, "0xd1", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) require.Equal(t, "0xb1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) }) t.Run("latest dropped below safe, then recovered", func(t *testing.T) { @@ -424,8 +424,8 @@ func TestConsensus(t *testing.T) { require.Equal(t, 1, len(consensusGroup)) require.Equal(t, "0xd1", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) require.Equal(t, "0xb1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) }) t.Run("latest dropped below safe, and stayed inconsistent", func(t *testing.T) { From 4ebc07656a1a57b86ac8301c50c5ee2d19123a04 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Tue, 30 May 2023 08:14:52 -0700 Subject: [PATCH 120/212] update readme --- proxyd/proxyd/README.md | 79 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/README.md b/proxyd/proxyd/README.md index ff019ea..346831f 100644 --- a/proxyd/proxyd/README.md +++ b/proxyd/proxyd/README.md @@ -5,7 +5,12 @@ This tool implements `proxyd`, an RPC request router and proxy. It does the foll 1. Whitelists RPC methods. 2. Routes RPC methods to groups of backend services. 3. Automatically retries failed backend requests. -4. Provides metrics the measure request latency, error rates, and the like. +4. Track backend consensus (`latest`, `safe`, `finalized` blocks), peer count and sync state. +5. Re-write requests and responses to enforce consensus. +6. Load balance requests across backend services. +7. Cache immutable responses from backends. +8. Provides metrics the measure request latency, error rates, and the like. + ## Usage @@ -15,12 +20,80 @@ To configure `proxyd` for use, you'll need to create a configuration file to def Once you have a config file, start the daemon via `proxyd .toml`. + +## Consensus awareness + +Starting on v4.0.0, `proxyd` is aware of the consensus state of its backends. This helps minimize chain reorgs experienced by clients. + +To enable this behavior, you must set `consensus_aware` value to `true` in the backend group. + +When consensus awareness is enabled, `proxyd` will poll the backends for their states and resolve a consensus group based on: +* the common ancestor `latest` block, i.e. if a backend is experiencing a fork, the fork won't be visible to the clients +* the lowest `safe` block +* the lowest `finalized` block +* peer count +* sync state + +The backend group then acts as a round-robin load balancer distributing traffic equally across healthy backends in the consensus group, increasing the availability of the proxy. + +A backend is considered healthy if it meets the following criteria: +* not banned +* avg 1-min moving window error rate ≤ configurable threshold +* avg 1-min moving window latency ≤ configurable threshold +* peer count ≥ configurable threshold +* `latest` block lag ≤ configurable threshold +* last state update ≤ configurable threshold +* not currently syncing + +When a backend is experiencing inconsistent consensus, high error rates or high latency, +the backend will be banned for a configurable amount of time (default 5 minutes) +and won't receive any traffic during this period. + + +## Tag rewrite + +When consensus awareness is enabled, `proxyd` will enforce the consensus state transparently for all the clients. + +For example, if a client requests the `eth_getBlockByNumber` method with the `latest` tag, +`proxyd` will rewrite the request to use the resolved latest block from the consensus group +and forward it to the backend. + +The following request methods are rewritten: +* `eth_getLogs` +* `eth_newFilter` +* `eth_getBalance` +* `eth_getCode` +* `eth_getTransactionCount` +* `eth_call` +* `eth_getStorageAt` +* `eth_getBlockTransactionCountByNumber` +* `eth_getUncleCountByBlockNumber` +* `eth_getBlockByNumber` +* `eth_getTransactionByBlockNumberAndIndex` +* `eth_getUncleByBlockNumberAndIndex` + +And `eth_blockNumber` response is overridden with current block consensus. + + +## Cacheable methods + +Cache use Redis and can be enabled for the following immutable methods: + +* `eth_chainId` +* `net_version` +* `eth_getBlockTransactionCountByHash` +* `eth_getUncleCountByBlockHash` +* `eth_getBlockByHash` +* `eth_getTransactionByBlockHashAndIndex` +* `eth_getUncleByBlockHashAndIndex` + + ## Metrics -See `metrics.go` for a list of all available metrics. +See `metrics.go` for a list of all available metrics. The metrics port is configurable via the `metrics.port` and `metrics.host` keys in the config. ## Adding Backend SSL Certificates in Docker -The Docker image runs on Alpine Linux. If you get SSL errors when connecting to a backend within Docker, you may need to add additional certificates to Alpine's certificate store. To do this, bind mount the certificate bundle into a file in `/usr/local/share/ca-certificates`. The `entrypoint.sh` script will then update the store with whatever is in the `ca-certificates` directory prior to starting `proxyd`. \ No newline at end of file +The Docker image runs on Alpine Linux. If you get SSL errors when connecting to a backend within Docker, you may need to add additional certificates to Alpine's certificate store. To do this, bind mount the certificate bundle into a file in `/usr/local/share/ca-certificates`. The `entrypoint.sh` script will then update the store with whatever is in the `ca-certificates` directory prior to starting `proxyd`. From a2e1667b17ca4a26b2376a1c1e62788b7747869d Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 31 May 2023 10:04:20 -0700 Subject: [PATCH 121/212] feat(proxyd): add debug_getRawReceipts to consensus --- proxyd/proxyd/README.md | 1 + proxyd/proxyd/rewriter.go | 14 +++++---- proxyd/proxyd/rewriter_test.go | 52 ++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/proxyd/proxyd/README.md b/proxyd/proxyd/README.md index 346831f..e0735c0 100644 --- a/proxyd/proxyd/README.md +++ b/proxyd/proxyd/README.md @@ -71,6 +71,7 @@ The following request methods are rewritten: * `eth_getBlockByNumber` * `eth_getTransactionByBlockNumberAndIndex` * `eth_getUncleByBlockNumberAndIndex` +* `debug_getRawReceipts` And `eth_blockNumber` response is overridden with current block consensus. diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index 08d5638..71dd361 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -63,24 +63,26 @@ func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResul case "eth_getLogs", "eth_newFilter": return rewriteRange(rctx, req, res, 0) + case "debug_getRawReceipts": + return rewriteParam(rctx, req, res, 0, true) case "eth_getBalance", "eth_getCode", "eth_getTransactionCount", "eth_call": - return rewriteParam(rctx, req, res, 1) + return rewriteParam(rctx, req, res, 1, false) case "eth_getStorageAt": - return rewriteParam(rctx, req, res, 2) + return rewriteParam(rctx, req, res, 2, false) case "eth_getBlockTransactionCountByNumber", "eth_getUncleCountByBlockNumber", "eth_getBlockByNumber", "eth_getTransactionByBlockNumberAndIndex", "eth_getUncleByBlockNumberAndIndex": - return rewriteParam(rctx, req, res, 0) + return rewriteParam(rctx, req, res, 0, false) } return RewriteNone, nil } -func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (RewriteResult, error) { +func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool) (RewriteResult, error) { var p []interface{} err := json.Unmarshal(req.Params, &p) if err != nil { @@ -89,9 +91,9 @@ func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (Rewri // we assume latest if the param is missing, // and we don't rewrite if there is not enough params - if len(p) == pos { + if len(p) == pos && !required { p = append(p, "latest") - } else if len(p) < pos { + } else if len(p) <= pos { return RewriteNone, nil } diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go index eb7f18f..087b804 100644 --- a/proxyd/proxyd/rewriter_test.go +++ b/proxyd/proxyd/rewriter_test.go @@ -148,6 +148,58 @@ func TestRewriteRequest(t *testing.T) { expected: RewriteOverrideError, expectedErr: ErrRewriteBlockOutOfRange, }, + /* required parameter at pos 0 */ + { + name: "debug_getRawReceipts latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{"latest"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(100).String(), p[0]) + }, + }, + { + name: "debug_getRawReceipts within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{hexutil.Uint64(55).String()})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(55).String(), p[0]) + }, + }, + { + name: "debug_getRawReceipts out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{hexutil.Uint64(111).String()})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + { + name: "debug_getRawReceipts missing parameter", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{})}, + res: nil, + }, + expected: RewriteNone, + }, /* default block parameter */ { name: "eth_getCode omit block, should add", From 64dab786cf6f2e2ce808be612580576b5f7bb05b Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 31 May 2023 10:48:21 -0700 Subject: [PATCH 122/212] add cache support for debug_getRawReceipts when request is for block hash --- proxyd/proxyd/README.md | 2 +- proxyd/proxyd/cache.go | 18 ++++++++++++++++++ proxyd/proxyd/cache_test.go | 23 +++++++++++++++++++++++ proxyd/proxyd/methods.go | 12 ++++++++++-- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/README.md b/proxyd/proxyd/README.md index e0735c0..d3bfd50 100644 --- a/proxyd/proxyd/README.md +++ b/proxyd/proxyd/README.md @@ -87,7 +87,7 @@ Cache use Redis and can be enabled for the following immutable methods: * `eth_getBlockByHash` * `eth_getTransactionByBlockHashAndIndex` * `eth_getUncleByBlockHashAndIndex` - +* `debug_getRawReceipts` (block hash only) ## Metrics diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 9684b75..0a88f7b 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -2,6 +2,8 @@ package proxyd import ( "context" + "encoding/json" + "github.com/ethereum/go-ethereum/rpc" "strings" "time" @@ -124,6 +126,21 @@ type rpcCache struct { func newRPCCache(cache Cache) RPCCache { staticHandler := &StaticMethodHandler{cache: cache} + debugGetRawReceiptsHandler := &StaticMethodHandler{cache: cache, + filter: func(req *RPCReq) bool { + // cache only if the request is for a block hash + + var p []rpc.BlockNumberOrHash + err := json.Unmarshal(req.Params, &p) + if err != nil { + return false + } + if len(p) != 1 { + return false + } + return p[0].BlockHash != nil + }, + } handlers := map[string]RPCMethodHandler{ "eth_chainId": staticHandler, "net_version": staticHandler, @@ -132,6 +149,7 @@ func newRPCCache(cache Cache) RPCCache { "eth_getBlockByHash": staticHandler, "eth_getTransactionByBlockHashAndIndex": staticHandler, "eth_getUncleByBlockHashAndIndex": staticHandler, + "debug_getRawReceipts": debugGetRawReceiptsHandler, } return &rpcCache{ cache: cache, diff --git a/proxyd/proxyd/cache_test.go b/proxyd/proxyd/cache_test.go index 727cdc7..93d0b8e 100644 --- a/proxyd/proxyd/cache_test.go +++ b/proxyd/proxyd/cache_test.go @@ -101,6 +101,20 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { }, name: "eth_getUncleByBlockHashAndIndex", }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "debug_getRawReceipts", + Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}), + ID: ID, + }, + res: &RPCRes{ + JSONRPC: "2.0", + Result: `{"debug_getRawReceipts":"!"}`, + ID: ID, + }, + name: "debug_getRawReceipts", + }, } for _, rpc := range rpcs { @@ -173,6 +187,15 @@ func TestRPCCacheUnsupportedMethod(t *testing.T) { ID: ID, }, }, + { + req: &RPCReq{ + JSONRPC: "2.0", + Method: "debug_getRawReceipts", + Params: mustMarshalJSON([]string{"0x100"}), + ID: ID, + }, + name: "debug_getRawReceipts", + }, } for _, rpc := range rpcs { diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index 60c0595..ffaf89d 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -17,8 +17,9 @@ type RPCMethodHandler interface { } type StaticMethodHandler struct { - cache Cache - m sync.RWMutex + cache Cache + m sync.RWMutex + filter func(*RPCReq) bool } func (e *StaticMethodHandler) key(req *RPCReq) string { @@ -33,6 +34,10 @@ func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*R if e.cache == nil { return nil, nil } + if e.filter != nil && !e.filter(req) { + return nil, nil + } + e.m.RLock() defer e.m.RUnlock() @@ -62,6 +67,9 @@ func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res if e.cache == nil { return nil } + if e.filter != nil && !e.filter(req) { + return nil + } e.m.Lock() defer e.m.Unlock() From 52ceaa581f0b1ea00d167f23e95931014f5d3935 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 31 May 2023 10:51:46 -0700 Subject: [PATCH 123/212] lint --- proxyd/proxyd/cache.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 0a88f7b..1cb8c38 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -3,10 +3,11 @@ package proxyd import ( "context" "encoding/json" - "github.com/ethereum/go-ethereum/rpc" "strings" "time" + "github.com/ethereum/go-ethereum/rpc" + "github.com/go-redis/redis/v8" "github.com/golang/snappy" lru "github.com/hashicorp/golang-lru" From 20648112b5c88e677945723665b7fbee0520d41d Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 31 May 2023 10:56:59 -0700 Subject: [PATCH 124/212] add test for debug_getRawReceipts with block hash --- proxyd/proxyd/rewriter_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go index 087b804..0637262 100644 --- a/proxyd/proxyd/rewriter_test.go +++ b/proxyd/proxyd/rewriter_test.go @@ -200,6 +200,22 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteNone, }, + { + name: "debug_getRawReceipts with block hash", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", p[0]) + }, + }, /* default block parameter */ { name: "eth_getCode omit block, should add", From a09795f83d246f44115aec7378fa9adce1a009ea Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 31 May 2023 12:41:20 -0700 Subject: [PATCH 125/212] metric for degraded backend --- .../integration_tests/consensus_test.go | 50 +++++++++++++++++++ .../integration_tests/testdata/consensus.toml | 1 + proxyd/proxyd/metrics.go | 9 ++++ 3 files changed, 60 insertions(+) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 8075637..27cdfb6 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -8,6 +8,7 @@ import ( "os" "path" "testing" + "time" "github.com/ethereum/go-ethereum/common/hexutil" @@ -630,6 +631,55 @@ func TestConsensus(t *testing.T) { require.Equal(t, len(nodes["node2"].mockBackend.Requests()), 0, msg) }) + t.Run("load balancing should not hit if node is degraded", func(t *testing.T) { + reset() + useOnlyNode1() + + // replace node1 handler with one that adds a 100ms delay + oldHandler := nodes["node1"].mockBackend.handler + defer func() { nodes["node1"].mockBackend.handler = oldHandler }() + + nodes["node1"].mockBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(100 * time.Millisecond) + oldHandler.ServeHTTP(w, r) + })) + + update() + + // send 10 requests - so the latency window should be at ~100ms + numberReqs := 10 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + numberReqs-- + } + + // bring back node2 + nodes["node2"].handler.ResetOverrides() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() + nodes["node2"].mockBackend.Reset() + + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) + require.Equal(t, 0, len(nodes["node2"].mockBackend.Requests())) + + numberReqs = 10 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + numberReqs-- + } + + msg := fmt.Sprintf("n1 %d, n2 %d", + len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) + require.Equal(t, len(nodes["node1"].mockBackend.Requests()), 0, msg) + require.Equal(t, len(nodes["node2"].mockBackend.Requests()), 10, msg) + }) + t.Run("rewrite response of eth_blockNumber", func(t *testing.T) { reset() update() diff --git a/proxyd/proxyd/integration_tests/testdata/consensus.toml b/proxyd/proxyd/integration_tests/testdata/consensus.toml index 03b11d0..6d8fdf5 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus.toml +++ b/proxyd/proxyd/integration_tests/testdata/consensus.toml @@ -3,6 +3,7 @@ rpc_port = 8545 [backend] response_timeout_seconds = 1 +max_degraded_latency_threshold = "30ms" [backends] [backends.node1] diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 1d9602c..0edd820 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -358,6 +358,14 @@ var ( "backend_name", }) + degradedBackends = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_degraded", + Help: "Bool gauge for degraded backends", + }, []string{ + "backend_name", + }) + networkErrorRateBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, Name: "backend_error_rate", @@ -493,6 +501,7 @@ func RecordConsensusBackendUpdateDelay(b *Backend, lastUpdate time.Time) { func RecordBackendNetworkLatencyAverageSlidingWindow(b *Backend, avgLatency time.Duration) { avgLatencyBackend.WithLabelValues(b.Name).Set(float64(avgLatency.Milliseconds())) + degradedBackends.WithLabelValues(b.Name).Set(boolToFloat64(b.IsDegraded())) } func RecordBackendNetworkErrorRateSlidingWindow(b *Backend, rate float64) { From a4d4d8750fcc7a421cfacfcc9d165e1f08b4cdc7 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 31 May 2023 13:13:31 -0700 Subject: [PATCH 126/212] delay 500ms instead of 100ms --- proxyd/proxyd/integration_tests/consensus_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 27cdfb6..06c8b79 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -640,13 +640,13 @@ func TestConsensus(t *testing.T) { defer func() { nodes["node1"].mockBackend.handler = oldHandler }() nodes["node1"].mockBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - time.Sleep(100 * time.Millisecond) + time.Sleep(500 * time.Millisecond) oldHandler.ServeHTTP(w, r) })) update() - // send 10 requests - so the latency window should be at ~100ms + // send 10 requests to make node1 degraded numberReqs := 10 for numberReqs > 0 { _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) @@ -676,8 +676,8 @@ func TestConsensus(t *testing.T) { msg := fmt.Sprintf("n1 %d, n2 %d", len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) - require.Equal(t, len(nodes["node1"].mockBackend.Requests()), 0, msg) - require.Equal(t, len(nodes["node2"].mockBackend.Requests()), 10, msg) + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests()), msg) + require.Equal(t, 10, len(nodes["node2"].mockBackend.Requests()), msg) }) t.Run("rewrite response of eth_blockNumber", func(t *testing.T) { From cb2492d661fe56ba479491cd5167fe2cc52fcbc1 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 31 May 2023 13:24:43 -0700 Subject: [PATCH 127/212] typo --- proxyd/proxyd/integration_tests/consensus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 06c8b79..f59358e 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -635,7 +635,7 @@ func TestConsensus(t *testing.T) { reset() useOnlyNode1() - // replace node1 handler with one that adds a 100ms delay + // replace node1 handler with one that adds a 500ms delay oldHandler := nodes["node1"].mockBackend.handler defer func() { nodes["node1"].mockBackend.handler = oldHandler }() From 33881542a99ff23474651444bb5d975bdb7c201b Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 1 Jun 2023 13:16:40 -0700 Subject: [PATCH 128/212] feat(proxyd): add consensus_getReceipts meta method --- proxyd/proxyd/README.md | 53 +++++++ proxyd/proxyd/backend.go | 150 ++++++++++++++++-- proxyd/proxyd/config.go | 26 +-- proxyd/proxyd/example.config.toml | 5 +- proxyd/proxyd/go.mod | 1 + proxyd/proxyd/go.sum | 2 + .../integration_tests/consensus_test.go | 146 +++++++++++++++++ .../integration_tests/testdata/consensus.toml | 1 + .../testdata/consensus_responses.yml | 27 ++++ proxyd/proxyd/proxyd.go | 26 ++- proxyd/proxyd/rewriter.go | 34 ++++ proxyd/proxyd/server.go | 7 + .../tools/mockserver/handler/handler.go | 7 +- 13 files changed, 458 insertions(+), 27 deletions(-) diff --git a/proxyd/proxyd/README.md b/proxyd/proxyd/README.md index d3bfd50..0a3c715 100644 --- a/proxyd/proxyd/README.md +++ b/proxyd/proxyd/README.md @@ -89,6 +89,59 @@ Cache use Redis and can be enabled for the following immutable methods: * `eth_getUncleByBlockHashAndIndex` * `debug_getRawReceipts` (block hash only) +## Meta method `consensus_getReceipts` + +To support backends with different specifications in the same backend group, +proxyd exposes a convenient method to fetch receipts abstracting away +what specific backend will serve the request. + +Each backend can specify their preferred method to fetch receipts with `consensus_receipts_target`. + +This method takes **both** the blockNumberOrHash **and** list of transaction hashes to fetch the receipts, +and then after selecting the backend to serve the request, +it translates to the correct target with the appropriate parameters. + +Note that only one of the parameters will be actually used depending on the target. + +Request params +```json +{ + "jsonrpc":"2.0", + "id": 1, + "params": { + "blockNumberOrHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", + "transactions": [ + "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", + "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b" + ] + } +} +``` + +It currently supports translation to the following targets: +* `debug_getRawReceipts(blockOrHash)` (default) +* `alchemy_getTransactionReceipts(blockOrHash)` +* `eth_getTransactionReceipt(txHash)` batched + +The selected target is returned in the response, in a wrapped result. + +Response +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "method": "eth_getTransactionReceipt", + "result": { + // the actual raw result from backend + } + } +} +``` + +See [op-node receipt fetcher](https://github.com/ethereum-optimism/optimism/blob/186e46a47647a51a658e699e9ff047d39444c2de/op-node/sources/receipts.go#L186-L253). + + ## Metrics See `metrics.go` for a list of all available metrics. diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 07c9fa2..439bb47 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -7,6 +7,9 @@ import ( "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + "github.com/google/uuid" "io" "math" "math/rand" @@ -97,6 +100,8 @@ var ( } ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") + + ErrConsensusGetReceiptsCantBeBatched = errors.New("consensus_getReceipts cannot be batched") ) func ErrInvalidRequest(msg string) *RPCErr { @@ -118,6 +123,7 @@ func ErrInvalidParams(msg string) *RPCErr { type Backend struct { Name string rpcURL string + receiptsTarget string wsURL string authUsername string authPassword string @@ -208,7 +214,7 @@ func WithProxydIP(ip string) BackendOpt { } } -func WithSkipPeerCountCheck(skipPeerCountCheck bool) BackendOpt { +func WithConsensusSkipPeerCountCheck(skipPeerCountCheck bool) BackendOpt { return func(b *Backend) { b.skipPeerCountCheck = skipPeerCountCheck } @@ -232,12 +238,33 @@ func WithMaxErrorRateThreshold(maxErrorRateThreshold float64) BackendOpt { } } +func WithConsensusReceiptTarget(receiptsTarget string) BackendOpt { + return func(b *Backend) { + b.receiptsTarget = receiptsTarget + } +} + type indexedReqRes struct { index int req *RPCReq res *RPCRes } +const ConsensusGetReceiptsMethod = "consensus_getReceipts" +const ReceiptsTargetEthTransactionReceipt = "eth_getTransactionReceipt" +const ReceiptsTargetDebugGetRawReceipts = "debug_getRawReceipts" +const ReceiptsTargetGetTransactionReceipts = "alchemy_getTransactionReceipts" + +type ConsensusGetReceiptsReq struct { + BlockOrHash *rpc.BlockNumberOrHash `json:"blockOrHash"` + Transactions []common.Hash `json:"transactions"` +} + +type ConsensusGetReceiptsRes struct { + Method string `json:"method"` + Result interface{} `json:"result"` +} + func NewBackend( name string, rpcURL string, @@ -266,9 +293,7 @@ func NewBackend( networkErrorsSlidingWindow: sw.NewSlidingWindow(), } - for _, opt := range opts { - opt(backend) - } + backend.Override(opts...) if !backend.stripTrailingXFF && backend.proxydIP == "" { log.Warn("proxied requests' XFF header will not contain the proxyd ip address") @@ -277,6 +302,12 @@ func NewBackend( return backend } +func (b *Backend) Override(opts ...BackendOpt) { + for _, opt := range opts { + opt(b) + } +} + func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { var lastError error // <= to account for the first attempt not technically being @@ -298,6 +329,13 @@ func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([] res, err := b.doForward(ctx, reqs, isBatch) switch err { case nil: // do nothing + case ErrConsensusGetReceiptsCantBeBatched: + log.Debug( + "Received unsupported batch request for consensus_getReceipts", + "name", b.Name, + "req_id", GetReqID(ctx), + "err", err, + ) // ErrBackendUnexpectedJSONRPC occurs because infura responds with a single JSON-RPC object // to a batch request whenever any Request Object in the batch would induce a partial error. // We don't label the backend offline in this case. But the error is still returned to @@ -375,11 +413,66 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // we are concerned about network error rates, so we record 1 request independently of how many are in the batch b.networkRequestsSlidingWindow.Incr() + originalRequests := rpcReqs + translatedReqs := make(map[string]*RPCReq, len(rpcReqs)) + derivedRequests := make([]*RPCReq, 0, 0) + // translate consensus_getReceipts to receipts target + // right now we only support non-batched + if !isBatch { + for _, rpcReq := range rpcReqs { + if rpcReq.Method == ConsensusGetReceiptsMethod { + translatedReqs[string(rpcReq.ID)] = rpcReq + rpcReq.Method = b.receiptsTarget + var reqParams []ConsensusGetReceiptsReq + err := json.Unmarshal(rpcReq.Params, &reqParams) + if err != nil { + return nil, ErrInvalidRequest("invalid request") + } + bnh := reqParams[0].BlockOrHash + switch b.receiptsTarget { + case ReceiptsTargetDebugGetRawReceipts, + ReceiptsTargetGetTransactionReceipts: // block or hash + params := make([]string, 1) + if bnh.BlockNumber != nil { + params[0] = bnh.BlockNumber.String() + } else { + params[0] = bnh.BlockHash.Hex() + } + rawParams := mustMarshalJSON(params) + rpcReq.Params = rawParams + case ReceiptsTargetEthTransactionReceipt: // list of tx hashes + for _, txHash := range reqParams[0].Transactions { + params := make([]common.Hash, 1) + params[0] = txHash + rawParams := mustMarshalJSON(params) + randomID := mustMarshalJSON(uuid.New().String()) + dReq := &RPCReq{ + JSONRPC: rpcReq.JSONRPC, + Method: ReceiptsTargetEthTransactionReceipt, + Params: rawParams, + ID: randomID, + } + derivedRequests = append(derivedRequests, dReq) + } + } + } + } + // replace the original request with the derived requests + if len(derivedRequests) > 0 { + rpcReqs = derivedRequests + } + } else { + for _, rpcReq := range rpcReqs { + if rpcReq.Method == ConsensusGetReceiptsMethod { + return nil, ErrConsensusGetReceiptsCantBeBatched + } + } + } + isSingleElementBatch := len(rpcReqs) == 1 // Single element batches are unwrapped before being sent // since Alchemy handles single requests better than batches. - var body []byte if isSingleElementBatch { body = mustMarshalJSON(rpcReqs[0]) @@ -443,17 +536,17 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool return nil, wrapErr(err, "error reading response body") } - var res []*RPCRes + var rpcRes []*RPCRes if isSingleElementBatch { var singleRes RPCRes if err := json.Unmarshal(resB, &singleRes); err != nil { return nil, ErrBackendBadResponse } - res = []*RPCRes{ + rpcRes = []*RPCRes{ &singleRes, } } else { - if err := json.Unmarshal(resB, &res); err != nil { + if err := json.Unmarshal(resB, &rpcRes); err != nil { // Infura may return a single JSON-RPC response if, for example, the batch contains a request for an unsupported method if responseIsNotBatched(resB) { b.networkErrorsSlidingWindow.Incr() @@ -466,7 +559,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool } } - if len(rpcReqs) != len(res) { + if len(rpcReqs) != len(rpcRes) { b.networkErrorsSlidingWindow.Incr() RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, ErrBackendUnexpectedJSONRPC @@ -475,7 +568,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // capture the HTTP status code in the response. this will only // ever be 400 given the status check on line 318 above. if httpRes.StatusCode != 200 { - for _, res := range res { + for _, res := range rpcRes { res.Error.HTTPErrorCode = httpRes.StatusCode } } @@ -484,8 +577,38 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool RecordBackendNetworkLatencyAverageSlidingWindow(b, time.Duration(b.latencySlidingWindow.Avg())) RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) - sortBatchRPCResponse(rpcReqs, res) - return res, nil + // enrich the response with the actual request method + for _, res := range rpcRes { + translatedReq, exist := translatedReqs[string(res.ID)] + if exist { + res.Result = ConsensusGetReceiptsRes{ + Method: translatedReq.Method, + Result: res.Result, + } + } + } + sortBatchRPCResponse(rpcReqs, rpcRes) + + // if the translated requests originated derived requests, wrap their results + if len(derivedRequests) > 0 { + results := make([]interface{}, 0, len(rpcRes)) + for _, res := range rpcRes { + results = append(results, res.Result) + } + + wrappedRes := &RPCRes{ + JSONRPC: originalRequests[0].JSONRPC, + Result: ConsensusGetReceiptsRes{ + Method: rpcReqs[0].Method, + Result: results, + }, + ID: originalRequests[0].ID, + } + + rpcRes = []*RPCRes{wrappedRes} + } + + return rpcRes, nil } // IsHealthy checks if the backend is able to serve traffic, based on dynamic parameters @@ -604,6 +727,9 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch if len(rpcReqs) > 0 { res, err = back.Forward(ctx, rpcReqs, isBatch) + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) { + return nil, err + } if errors.Is(err, ErrMethodNotWhitelisted) { return nil, err } diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 0e75769..bd58f62 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -79,18 +79,20 @@ type BackendOptions struct { } type BackendConfig struct { - Username string `toml:"username"` - Password string `toml:"password"` - RPCURL string `toml:"rpc_url"` - WSURL string `toml:"ws_url"` - WSPort int `toml:"ws_port"` - MaxRPS int `toml:"max_rps"` - MaxWSConns int `toml:"max_ws_conns"` - CAFile string `toml:"ca_file"` - ClientCertFile string `toml:"client_cert_file"` - ClientKeyFile string `toml:"client_key_file"` - StripTrailingXFF bool `toml:"strip_trailing_xff"` - SkipPeerCountCheck bool `toml:"consensus_skip_peer_count"` + Username string `toml:"username"` + Password string `toml:"password"` + RPCURL string `toml:"rpc_url"` + WSURL string `toml:"ws_url"` + WSPort int `toml:"ws_port"` + MaxRPS int `toml:"max_rps"` + MaxWSConns int `toml:"max_ws_conns"` + CAFile string `toml:"ca_file"` + ClientCertFile string `toml:"client_cert_file"` + ClientKeyFile string `toml:"client_key_file"` + StripTrailingXFF bool `toml:"strip_trailing_xff"` + + ConsensusSkipPeerCountCheck bool `toml:"consensus_skip_peer_count"` + ConsensusReceiptsTarget string `toml:"consensus_receipts_target"` } type BackendsConfig map[string]*BackendConfig diff --git a/proxyd/proxyd/example.config.toml b/proxyd/proxyd/example.config.toml index 19c6c26..cce4896 100644 --- a/proxyd/proxyd/example.config.toml +++ b/proxyd/proxyd/example.config.toml @@ -74,7 +74,9 @@ client_cert_file = "" client_key_file = "" # Allows backends to skip peer count checking, default false # consensus_skip_peer_count = true - +# Specified the target method to get receipts, default "debug_getRawReceipts" +# See https://github.com/ethereum-optimism/optimism/blob/186e46a47647a51a658e699e9ff047d39444c2de/op-node/sources/receipts.go#L186-L253 +consensus_receipts_target = "eth_getBlockReceipts" [backends.alchemy] rpc_url = "" @@ -83,6 +85,7 @@ username = "" password = "" max_rps = 3 max_ws_conns = 1 +consensus_receipts_target = "alchemy_getTransactionReceipts" [backend_groups] [backend_groups.main] diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index d7b1c89..605a887 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -9,6 +9,7 @@ require ( github.com/ethereum/go-ethereum v1.12.0 github.com/go-redis/redis/v8 v8.11.4 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb + github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index c9927a4..a6edcee 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -157,6 +157,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index f59358e..b089d3d 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -784,6 +784,152 @@ func TestConsensus(t *testing.T) { // dont rewrite for 0xe1 require.Equal(t, "0xe1", jsonMap[2]["result"].(map[string]interface{})["number"]) }) + + t.Run("translate consensus_getReceipts to debug_getRawReceipts", func(t *testing.T) { + reset() + useOnlyNode1() + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ + "blockOrHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "debug_getRawReceipts", jsonMap["method"]) + require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", jsonMap["params"].([]interface{})[0]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to debug_getRawReceipts with latest block tag", func(t *testing.T) { + reset() + useOnlyNode1() + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ + "blockOrHash": "latest"}}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "debug_getRawReceipts", jsonMap["method"]) + require.Equal(t, "0x101", jsonMap["params"].([]interface{})[0]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to debug_getRawReceipts with block number", func(t *testing.T) { + reset() + useOnlyNode1() + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ + "blockOrHash": "0x55"}}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "debug_getRawReceipts", jsonMap["method"]) + require.Equal(t, "0x55", jsonMap["params"].([]interface{})[0]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts", func(t *testing.T) { + reset() + useOnlyNode1() + + nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) + defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ + "blockOrHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var reqJsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) + + require.NoError(t, err) + require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) + require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", reqJsonMap["params"].([]interface{})[0]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to eth_getTransactionReceipt batched", func(t *testing.T) { + reset() + useOnlyNode1() + + nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("eth_getTransactionReceipt")) + defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ + "blockOrHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", + "transactions": []string{ + "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5", + "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c6", + "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c7", + "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c8", + }}}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var reqJsonMap []map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) + + require.NoError(t, err) + require.Equal(t, 4, len(reqJsonMap)) + for _, req := range reqJsonMap { + require.Equal(t, "eth_getTransactionReceipt", req["method"]) + } + require.Equal(t, "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5", reqJsonMap[0]["params"].([]interface{})[0]) + require.Equal(t, "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c6", reqJsonMap[1]["params"].([]interface{})[0]) + require.Equal(t, "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c7", reqJsonMap[2]["params"].([]interface{})[0]) + require.Equal(t, "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c8", reqJsonMap[3]["params"].([]interface{})[0]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + + require.Equal(t, "eth_getTransactionReceipt", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, 4, len(resJsonMap["result"].(map[string]interface{})["result"].([]interface{}))) + for _, res := range resJsonMap["result"].(map[string]interface{})["result"].([]interface{}) { + require.Equal(t, "eth_getTransactionReceipt", res.(map[string]interface{})["_"]) + } + + }) + + t.Run("consensus_getReceipts should not be used in a batch", func(t *testing.T) { + reset() + useOnlyNode1() + + _, statusCode, err := client.SendBatchRPC( + NewRPCReq("1", "eth_getBlockByNumber", []interface{}{"latest"}), + NewRPCReq("2", "consensus_getReceipts", []interface{}{"0x102"}), + NewRPCReq("3", "eth_getBlockByNumber", []interface{}{"0xe1"})) + require.NoError(t, err) + require.Equal(t, 400, statusCode) + }) } func buildResponse(result interface{}) string { diff --git a/proxyd/proxyd/integration_tests/testdata/consensus.toml b/proxyd/proxyd/integration_tests/testdata/consensus.toml index 6d8fdf5..bb13036 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus.toml +++ b/proxyd/proxyd/integration_tests/testdata/consensus.toml @@ -27,3 +27,4 @@ eth_call = "node" eth_chainId = "node" eth_blockNumber = "node" eth_getBlockByNumber = "node" +consensus_getReceipts = "node" diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml index ee413c5..e4720b0 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml +++ b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml @@ -184,3 +184,30 @@ "number": "0xd1" } } +- method: debug_getRawReceipts + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "debug_getRawReceipts" + } + } +- method: eth_getTransactionReceipt + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "eth_getTransactionReceipt" + } + } +- method: alchemy_getTransactionReceipts + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "alchemy_getTransactionReceipts" + } + } diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index afb27bc..4a523f6 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -141,7 +141,17 @@ func Start(config *Config) (*Server, func(), error) { opts = append(opts, WithStrippedTrailingXFF()) } opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP"))) - opts = append(opts, WithSkipPeerCountCheck(cfg.SkipPeerCountCheck)) + opts = append(opts, WithConsensusSkipPeerCountCheck(cfg.ConsensusSkipPeerCountCheck)) + + receiptsTarget, err := ReadFromEnvOrConfig(cfg.ConsensusReceiptsTarget) + if err != nil { + return nil, nil, err + } + receiptsTarget, err = validateReceiptsTarget(receiptsTarget) + if err != nil { + return nil, nil, err + } + opts = append(opts, WithConsensusReceiptTarget(receiptsTarget)) back := NewBackend(name, rpcURL, wsURL, rpcRequestSemaphore, opts...) backendNames = append(backendNames, name) @@ -316,6 +326,20 @@ func Start(config *Config) (*Server, func(), error) { return srv, shutdownFunc, nil } +func validateReceiptsTarget(val string) (string, error) { + if val == "" { + val = "debug_getRawReceipts" + } + switch val { + case "debug_getRawReceipts", + "eth_getTransactionReceipt", + "alchemy_getTransactionReceipts": + return val, nil + default: + return "", fmt.Errorf("invalid receipts target: %s", val) + } +} + func secondsToDuration(seconds int) time.Duration { return time.Duration(seconds) * time.Second } diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index 71dd361..6c2840b 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -63,6 +63,8 @@ func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResul case "eth_getLogs", "eth_newFilter": return rewriteRange(rctx, req, res, 0) + case "consensus_getReceipts": + return rewriteGetReceiptsParams(rctx, req, res) case "debug_getRawReceipts": return rewriteParam(rctx, req, res, 0, true) case "eth_getBalance", @@ -82,6 +84,38 @@ func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResul return RewriteNone, nil } +func rewriteGetReceiptsParams(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { + var p []interface{} + err := json.Unmarshal(req.Params, &p) + if err != nil { + return RewriteOverrideError, err + } + + if len(p) != 1 { + return RewriteNone, nil + } + + if m, ok := p[0].(map[string]interface{}); !ok || m["blockOrHash"] == nil { + return RewriteNone, nil + } + + val, rw, err := rewriteTag(rctx, p[0].(map[string]interface{})["blockOrHash"].(string)) + if err != nil { + return RewriteOverrideError, err + } + + if rw { + p[0].(map[string]interface{})["blockOrHash"] = val + paramRaw, err := json.Marshal(p) + if err != nil { + return RewriteOverrideError, err + } + req.Params = paramRaw + return RewriteOverrideRequest, nil + } + return RewriteNone, nil +} + func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool) (RewriteResult, error) { var p []interface{} err := json.Unmarshal(req.Params, &p) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 3ca10af..8b3a2e8 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -347,6 +347,10 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { writeRPCError(ctx, w, nil, ErrGatewayTimeout) return } + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) { + writeRPCError(ctx, w, nil, ErrInvalidRequest(err.Error())) + return + } if err != nil { writeRPCError(ctx, w, nil, ErrInternal) return @@ -485,6 +489,9 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL elems := cacheMisses[start:end] res, err := s.BackendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) if err != nil { + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) { + return nil, false, err + } log.Error( "error forwarding RPC batch", "batch_size", len(elems), diff --git a/proxyd/proxyd/tools/mockserver/handler/handler.go b/proxyd/proxyd/tools/mockserver/handler/handler.go index 8a78a7e..99c28c5 100644 --- a/proxyd/proxyd/tools/mockserver/handler/handler.go +++ b/proxyd/proxyd/tools/mockserver/handler/handler.go @@ -88,7 +88,12 @@ func (mh *MockedHandler) Handler(w http.ResponseWriter, req *http.Request) { } } if selectedResponse != "" { - responses = append(responses, selectedResponse) + var rpcRes proxyd.RPCRes + err = json.Unmarshal([]byte(selectedResponse), &rpcRes) + idJson, _ := json.Marshal(r["id"]) + rpcRes.ID = idJson + res, _ := json.Marshal(rpcRes) + responses = append(responses, string(res)) } } From eb0fc1a837bd1d6aef802e1ef5c2fce890334063 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 1 Jun 2023 13:36:13 -0700 Subject: [PATCH 129/212] lint --- proxyd/proxyd/backend.go | 9 +++++---- proxyd/proxyd/integration_tests/consensus_test.go | 2 ++ proxyd/proxyd/tools/mockserver/handler/handler.go | 3 +++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 439bb47..6bc92e4 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -7,9 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rpc" - "github.com/google/uuid" "io" "math" "math/rand" @@ -20,6 +17,10 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + "github.com/google/uuid" + sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" "github.com/ethereum/go-ethereum/log" @@ -415,7 +416,7 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool originalRequests := rpcReqs translatedReqs := make(map[string]*RPCReq, len(rpcReqs)) - derivedRequests := make([]*RPCReq, 0, 0) + derivedRequests := make([]*RPCReq, 0) // translate consensus_getReceipts to receipts target // right now we only support non-batched if !isBatch { diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index b089d3d..a0ae8d4 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -802,6 +802,7 @@ func TestConsensus(t *testing.T) { var resJsonMap map[string]interface{} err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) @@ -824,6 +825,7 @@ func TestConsensus(t *testing.T) { var resJsonMap map[string]interface{} err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) diff --git a/proxyd/proxyd/tools/mockserver/handler/handler.go b/proxyd/proxyd/tools/mockserver/handler/handler.go index 99c28c5..0f6880c 100644 --- a/proxyd/proxyd/tools/mockserver/handler/handler.go +++ b/proxyd/proxyd/tools/mockserver/handler/handler.go @@ -90,6 +90,9 @@ func (mh *MockedHandler) Handler(w http.ResponseWriter, req *http.Request) { if selectedResponse != "" { var rpcRes proxyd.RPCRes err = json.Unmarshal([]byte(selectedResponse), &rpcRes) + if err != nil { + panic(err) + } idJson, _ := json.Marshal(r["id"]) rpcRes.ID = idJson res, _ := json.Marshal(rpcRes) From 924030dd707b9d8e507954d2cd1f30871d0c07fc Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 1 Jun 2023 13:40:03 -0700 Subject: [PATCH 130/212] lint --- proxyd/proxyd/integration_tests/consensus_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index a0ae8d4..f55dbe9 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -848,6 +848,7 @@ func TestConsensus(t *testing.T) { var resJsonMap map[string]interface{} err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) @@ -874,6 +875,7 @@ func TestConsensus(t *testing.T) { var resJsonMap map[string]interface{} err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) @@ -912,6 +914,7 @@ func TestConsensus(t *testing.T) { var resJsonMap map[string]interface{} err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) require.Equal(t, "eth_getTransactionReceipt", resJsonMap["result"].(map[string]interface{})["method"].(string)) require.Equal(t, 4, len(resJsonMap["result"].(map[string]interface{})["result"].([]interface{}))) From a3cf38f8b268b01ec04ef51c6bd5b40a69732b86 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 2 Jun 2023 13:07:18 -0700 Subject: [PATCH 131/212] translate only methods with block params --- proxyd/proxyd/README.md | 27 ++-- proxyd/proxyd/backend.go | 121 +++++++++--------- .../integration_tests/consensus_test.go | 98 ++++++++++---- proxyd/proxyd/server.go | 11 +- 4 files changed, 149 insertions(+), 108 deletions(-) diff --git a/proxyd/proxyd/README.md b/proxyd/proxyd/README.md index 0a3c715..e3ec1ca 100644 --- a/proxyd/proxyd/README.md +++ b/proxyd/proxyd/README.md @@ -95,43 +95,36 @@ To support backends with different specifications in the same backend group, proxyd exposes a convenient method to fetch receipts abstracting away what specific backend will serve the request. -Each backend can specify their preferred method to fetch receipts with `consensus_receipts_target`. +Each backend specifies their preferred method to fetch receipts with `consensus_receipts_target` config, +which will be translated from `consensus_getReceipts`. -This method takes **both** the blockNumberOrHash **and** list of transaction hashes to fetch the receipts, -and then after selecting the backend to serve the request, -it translates to the correct target with the appropriate parameters. +This method takes a `blockNumberOrHash` (i.e. `tag|qty|hash`) +and returns the receipts for all transactions in the block. -Note that only one of the parameters will be actually used depending on the target. - -Request params +Request example ```json { "jsonrpc":"2.0", "id": 1, - "params": { - "blockNumberOrHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", - "transactions": [ - "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", - "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b" - ] - } + "params": ["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"] } ``` It currently supports translation to the following targets: * `debug_getRawReceipts(blockOrHash)` (default) * `alchemy_getTransactionReceipts(blockOrHash)` -* `eth_getTransactionReceipt(txHash)` batched +* `parity_getBlockReceipts(blockOrHash)` +* `eth_getBlockReceipts(blockOrHash)` The selected target is returned in the response, in a wrapped result. -Response +Response example ```json { "jsonrpc": "2.0", "id": 1, "result": { - "method": "eth_getTransactionReceipt", + "method": "debug_getRawReceipts", "result": { // the actual raw result from backend } diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 6bc92e4..6fc1552 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -17,11 +17,9 @@ import ( "sync" "time" + sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" - "github.com/google/uuid" - - sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" "github.com/ethereum/go-ethereum/log" "github.com/gorilla/websocket" @@ -103,6 +101,7 @@ var ( ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") ErrConsensusGetReceiptsCantBeBatched = errors.New("consensus_getReceipts cannot be batched") + ErrConsensusGetReceiptsInvalidTarget = errors.New("unsupported consensus_receipts_target") ) func ErrInvalidRequest(msg string) *RPCErr { @@ -252,20 +251,28 @@ type indexedReqRes struct { } const ConsensusGetReceiptsMethod = "consensus_getReceipts" -const ReceiptsTargetEthTransactionReceipt = "eth_getTransactionReceipt" -const ReceiptsTargetDebugGetRawReceipts = "debug_getRawReceipts" -const ReceiptsTargetGetTransactionReceipts = "alchemy_getTransactionReceipts" -type ConsensusGetReceiptsReq struct { +const ReceiptsTargetDebugGetRawReceipts = "debug_getRawReceipts" +const ReceiptsTargetAlchemyGetTransactionReceipts = "alchemy_getTransactionReceipts" +const ReceiptsTargetParityGetTransactionReceipts = "parity_getBlockReceipts" +const ReceiptsTargetEthGetTransactionReceipts = "eth_getBlockReceipts" + +type ConsensusGetReceiptsRequest struct { BlockOrHash *rpc.BlockNumberOrHash `json:"blockOrHash"` Transactions []common.Hash `json:"transactions"` } -type ConsensusGetReceiptsRes struct { +type ConsensusGetReceiptsResult struct { Method string `json:"method"` Result interface{} `json:"result"` } +// BlockHashOrNumberParameter is a non-conventional wrapper used by alchemy_getTransactionReceipts +type BlockHashOrNumberParameter struct { + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *rpc.BlockNumber `json:"blockNumber"` +} + func NewBackend( name string, rpcURL string, @@ -331,12 +338,19 @@ func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([] switch err { case nil: // do nothing case ErrConsensusGetReceiptsCantBeBatched: - log.Debug( + log.Warn( "Received unsupported batch request for consensus_getReceipts", "name", b.Name, "req_id", GetReqID(ctx), "err", err, ) + case ErrConsensusGetReceiptsInvalidTarget: + log.Error( + "Unsupported consensus_receipts_target for consensus_getReceipts", + "name", b.Name, + "req_id", GetReqID(ctx), + "err", err, + ) // ErrBackendUnexpectedJSONRPC occurs because infura responds with a single JSON-RPC object // to a batch request whenever any Request Object in the batch would induce a partial error. // We don't label the backend offline in this case. But the error is still returned to @@ -414,58 +428,56 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // we are concerned about network error rates, so we record 1 request independently of how many are in the batch b.networkRequestsSlidingWindow.Incr() - originalRequests := rpcReqs translatedReqs := make(map[string]*RPCReq, len(rpcReqs)) - derivedRequests := make([]*RPCReq, 0) // translate consensus_getReceipts to receipts target // right now we only support non-batched - if !isBatch { + if isBatch { + for _, rpcReq := range rpcReqs { + if rpcReq.Method == ConsensusGetReceiptsMethod { + return nil, ErrConsensusGetReceiptsCantBeBatched + } + } + } else { for _, rpcReq := range rpcReqs { if rpcReq.Method == ConsensusGetReceiptsMethod { translatedReqs[string(rpcReq.ID)] = rpcReq rpcReq.Method = b.receiptsTarget - var reqParams []ConsensusGetReceiptsReq + var reqParams []ConsensusGetReceiptsRequest err := json.Unmarshal(rpcReq.Params, &reqParams) if err != nil { return nil, ErrInvalidRequest("invalid request") } bnh := reqParams[0].BlockOrHash - switch b.receiptsTarget { + + var translatedParams []byte + switch rpcReq.Method { case ReceiptsTargetDebugGetRawReceipts, - ReceiptsTargetGetTransactionReceipts: // block or hash + ReceiptsTargetEthGetTransactionReceipts, + ReceiptsTargetParityGetTransactionReceipts: + // conventional methods use an array of strings having either block number or block hash + // i.e. ["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"] params := make([]string, 1) if bnh.BlockNumber != nil { params[0] = bnh.BlockNumber.String() } else { params[0] = bnh.BlockHash.Hex() } - rawParams := mustMarshalJSON(params) - rpcReq.Params = rawParams - case ReceiptsTargetEthTransactionReceipt: // list of tx hashes - for _, txHash := range reqParams[0].Transactions { - params := make([]common.Hash, 1) - params[0] = txHash - rawParams := mustMarshalJSON(params) - randomID := mustMarshalJSON(uuid.New().String()) - dReq := &RPCReq{ - JSONRPC: rpcReq.JSONRPC, - Method: ReceiptsTargetEthTransactionReceipt, - Params: rawParams, - ID: randomID, - } - derivedRequests = append(derivedRequests, dReq) + translatedParams = mustMarshalJSON(params) + case ReceiptsTargetAlchemyGetTransactionReceipts: + // alchemy uses an array of object with either block number or block hash + // i.e. [{ blockHash: "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b" }] + params := make([]BlockHashOrNumberParameter, 1) + if bnh.BlockNumber != nil { + params[0].BlockNumber = bnh.BlockNumber + } else { + params[0].BlockHash = bnh.BlockHash } + translatedParams = mustMarshalJSON(params) + default: + return nil, ErrConsensusGetReceiptsInvalidTarget } - } - } - // replace the original request with the derived requests - if len(derivedRequests) > 0 { - rpcReqs = derivedRequests - } - } else { - for _, rpcReq := range rpcReqs { - if rpcReq.Method == ConsensusGetReceiptsMethod { - return nil, ErrConsensusGetReceiptsCantBeBatched + + rpcReq.Params = translatedParams } } } @@ -582,33 +594,15 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool for _, res := range rpcRes { translatedReq, exist := translatedReqs[string(res.ID)] if exist { - res.Result = ConsensusGetReceiptsRes{ + res.Result = ConsensusGetReceiptsResult{ Method: translatedReq.Method, Result: res.Result, } } } + sortBatchRPCResponse(rpcReqs, rpcRes) - // if the translated requests originated derived requests, wrap their results - if len(derivedRequests) > 0 { - results := make([]interface{}, 0, len(rpcRes)) - for _, res := range rpcRes { - results = append(results, res.Result) - } - - wrappedRes := &RPCRes{ - JSONRPC: originalRequests[0].JSONRPC, - Result: ConsensusGetReceiptsRes{ - Method: rpcReqs[0].Method, - Result: results, - }, - ID: originalRequests[0].ID, - } - - rpcRes = []*RPCRes{wrappedRes} - } - return rpcRes, nil } @@ -728,10 +722,9 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch if len(rpcReqs) > 0 { res, err = back.Forward(ctx, rpcReqs, isBatch) - if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) { - return nil, err - } - if errors.Is(err, ErrMethodNotWhitelisted) { + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || + errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) || + errors.Is(err, ErrMethodNotWhitelisted) { return nil, err } if errors.Is(err, ErrBackendOffline) { diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index f55dbe9..f4166a3 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -788,6 +788,10 @@ func TestConsensus(t *testing.T) { t.Run("translate consensus_getReceipts to debug_getRawReceipts", func(t *testing.T) { reset() useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ "blockOrHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}}) @@ -811,6 +815,10 @@ func TestConsensus(t *testing.T) { t.Run("translate consensus_getReceipts to debug_getRawReceipts with latest block tag", func(t *testing.T) { reset() useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ "blockOrHash": "latest"}}) @@ -834,6 +842,10 @@ func TestConsensus(t *testing.T) { t.Run("translate consensus_getReceipts to debug_getRawReceipts with block number", func(t *testing.T) { reset() useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ "blockOrHash": "0x55"}}) @@ -854,9 +866,13 @@ func TestConsensus(t *testing.T) { require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) }) - t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts", func(t *testing.T) { + t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts with block hash", func(t *testing.T) { reset() useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) @@ -871,7 +887,7 @@ func TestConsensus(t *testing.T) { require.NoError(t, err) require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) - require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", reqJsonMap["params"].([]interface{})[0]) + require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", reqJsonMap["params"].([]interface{})[0].(map[string]interface{})["blockHash"]) var resJsonMap map[string]interface{} err = json.Unmarshal(resRaw, &resJsonMap) @@ -881,47 +897,79 @@ func TestConsensus(t *testing.T) { require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) }) - t.Run("translate consensus_getReceipts to eth_getTransactionReceipt batched", func(t *testing.T) { + t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts with block number", func(t *testing.T) { reset() useOnlyNode1() + update() - nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("eth_getTransactionReceipt")) + // reset request counts + nodes["node1"].mockBackend.Reset() + + nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ - "blockOrHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", - "transactions": []string{ - "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5", - "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c6", - "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c7", - "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c8", - }}}) + "blockOrHash": "0x55"}}) require.NoError(t, err) require.Equal(t, 200, statusCode) - var reqJsonMap []map[string]interface{} + var reqJsonMap map[string]interface{} err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) require.NoError(t, err) - require.Equal(t, 4, len(reqJsonMap)) - for _, req := range reqJsonMap { - require.Equal(t, "eth_getTransactionReceipt", req["method"]) - } - require.Equal(t, "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5", reqJsonMap[0]["params"].([]interface{})[0]) - require.Equal(t, "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c6", reqJsonMap[1]["params"].([]interface{})[0]) - require.Equal(t, "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c7", reqJsonMap[2]["params"].([]interface{})[0]) - require.Equal(t, "0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c8", reqJsonMap[3]["params"].([]interface{})[0]) + require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) + require.Equal(t, "0x55", reqJsonMap["params"].([]interface{})[0].(map[string]interface{})["blockNumber"]) var resJsonMap map[string]interface{} err = json.Unmarshal(resRaw, &resJsonMap) require.NoError(t, err) - require.Equal(t, "eth_getTransactionReceipt", resJsonMap["result"].(map[string]interface{})["method"].(string)) - require.Equal(t, 4, len(resJsonMap["result"].(map[string]interface{})["result"].([]interface{}))) - for _, res := range resJsonMap["result"].(map[string]interface{})["result"].([]interface{}) { - require.Equal(t, "eth_getTransactionReceipt", res.(map[string]interface{})["_"]) - } + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts with latest block tag", func(t *testing.T) { + reset() + useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() + + nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) + defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ + "blockOrHash": "latest"}}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var reqJsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) + + require.NoError(t, err) + require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) + require.Equal(t, "0x101", reqJsonMap["params"].([]interface{})[0].(map[string]interface{})["blockNumber"]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) + + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to unsupported consensus_receipts_target", func(t *testing.T) { + reset() + useOnlyNode1() + + nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("unsupported_consensus_receipts_target")) + defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) + + _, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ + "blockOrHash": "latest"}}) + require.NoError(t, err) + require.Equal(t, 400, statusCode) }) t.Run("consensus_getReceipts should not be used in a batch", func(t *testing.T) { diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 8b3a2e8..88b031c 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -347,7 +347,8 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { writeRPCError(ctx, w, nil, ErrGatewayTimeout) return } - if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) { + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || + errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { writeRPCError(ctx, w, nil, ErrInvalidRequest(err.Error())) return } @@ -364,6 +365,11 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { rawBody := json.RawMessage(body) backendRes, cached, err := s.handleBatchRPC(ctx, []json.RawMessage{rawBody}, isLimited, false) if err != nil { + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || + errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { + writeRPCError(ctx, w, nil, ErrInvalidRequest(err.Error())) + return + } writeRPCError(ctx, w, nil, ErrInternal) return } @@ -489,7 +495,8 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL elems := cacheMisses[start:end] res, err := s.BackendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) if err != nil { - if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) { + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || + errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { return nil, false, err } log.Error( From 804a57566f8da74dd38c7ee79cf1ba6674e37de7 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 2 Jun 2023 13:45:42 -0700 Subject: [PATCH 132/212] fix input --- proxyd/proxyd/backend.go | 20 ++++------- .../integration_tests/consensus_test.go | 36 +++++++++++-------- proxyd/proxyd/rewriter.go | 36 +------------------ 3 files changed, 29 insertions(+), 63 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 6fc1552..9c4234f 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -257,11 +257,6 @@ const ReceiptsTargetAlchemyGetTransactionReceipts = "alchemy_getTransactionRecei const ReceiptsTargetParityGetTransactionReceipts = "parity_getBlockReceipts" const ReceiptsTargetEthGetTransactionReceipts = "eth_getBlockReceipts" -type ConsensusGetReceiptsRequest struct { - BlockOrHash *rpc.BlockNumberOrHash `json:"blockOrHash"` - Transactions []common.Hash `json:"transactions"` -} - type ConsensusGetReceiptsResult struct { Method string `json:"method"` Result interface{} `json:"result"` @@ -442,12 +437,11 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool if rpcReq.Method == ConsensusGetReceiptsMethod { translatedReqs[string(rpcReq.ID)] = rpcReq rpcReq.Method = b.receiptsTarget - var reqParams []ConsensusGetReceiptsRequest + var reqParams []rpc.BlockNumberOrHash err := json.Unmarshal(rpcReq.Params, &reqParams) if err != nil { return nil, ErrInvalidRequest("invalid request") } - bnh := reqParams[0].BlockOrHash var translatedParams []byte switch rpcReq.Method { @@ -457,20 +451,20 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // conventional methods use an array of strings having either block number or block hash // i.e. ["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"] params := make([]string, 1) - if bnh.BlockNumber != nil { - params[0] = bnh.BlockNumber.String() + if reqParams[0].BlockNumber != nil { + params[0] = reqParams[0].BlockNumber.String() } else { - params[0] = bnh.BlockHash.Hex() + params[0] = reqParams[0].BlockHash.Hex() } translatedParams = mustMarshalJSON(params) case ReceiptsTargetAlchemyGetTransactionReceipts: // alchemy uses an array of object with either block number or block hash // i.e. [{ blockHash: "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b" }] params := make([]BlockHashOrNumberParameter, 1) - if bnh.BlockNumber != nil { - params[0].BlockNumber = bnh.BlockNumber + if reqParams[0].BlockNumber != nil { + params[0].BlockNumber = reqParams[0].BlockNumber } else { - params[0].BlockHash = bnh.BlockHash + params[0].BlockHash = reqParams[0].BlockHash } translatedParams = mustMarshalJSON(params) default: diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index f4166a3..1b37ef7 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -793,8 +793,8 @@ func TestConsensus(t *testing.T) { // reset request counts nodes["node1"].mockBackend.Reset() - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ - "blockOrHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}}) + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}) require.NoError(t, err) require.Equal(t, 200, statusCode) @@ -820,8 +820,9 @@ func TestConsensus(t *testing.T) { // reset request counts nodes["node1"].mockBackend.Reset() - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ - "blockOrHash": "latest"}}) + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"latest"}) + require.NoError(t, err) require.Equal(t, 200, statusCode) @@ -847,8 +848,9 @@ func TestConsensus(t *testing.T) { // reset request counts nodes["node1"].mockBackend.Reset() - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ - "blockOrHash": "0x55"}}) + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"0x55"}) + require.NoError(t, err) require.Equal(t, 200, statusCode) @@ -877,8 +879,9 @@ func TestConsensus(t *testing.T) { nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ - "blockOrHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}}) + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}) + require.NoError(t, err) require.Equal(t, 200, statusCode) @@ -908,8 +911,9 @@ func TestConsensus(t *testing.T) { nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ - "blockOrHash": "0x55"}}) + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"0x55"}) + require.NoError(t, err) require.Equal(t, 200, statusCode) @@ -939,8 +943,9 @@ func TestConsensus(t *testing.T) { nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ - "blockOrHash": "latest"}}) + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"latest"}) + require.NoError(t, err) require.Equal(t, 200, statusCode) @@ -966,8 +971,9 @@ func TestConsensus(t *testing.T) { nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("unsupported_consensus_receipts_target")) defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) - _, statusCode, err := client.SendRPC("consensus_getReceipts", []interface{}{map[string]interface{}{ - "blockOrHash": "latest"}}) + _, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"latest"}) + require.NoError(t, err) require.Equal(t, 400, statusCode) }) @@ -978,7 +984,7 @@ func TestConsensus(t *testing.T) { _, statusCode, err := client.SendBatchRPC( NewRPCReq("1", "eth_getBlockByNumber", []interface{}{"latest"}), - NewRPCReq("2", "consensus_getReceipts", []interface{}{"0x102"}), + NewRPCReq("2", "consensus_getReceipts", []interface{}{"0x55"}), NewRPCReq("3", "eth_getBlockByNumber", []interface{}{"0xe1"})) require.NoError(t, err) require.Equal(t, 400, statusCode) diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index 6c2840b..b00bc51 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -63,9 +63,7 @@ func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResul case "eth_getLogs", "eth_newFilter": return rewriteRange(rctx, req, res, 0) - case "consensus_getReceipts": - return rewriteGetReceiptsParams(rctx, req, res) - case "debug_getRawReceipts": + case "debug_getRawReceipts", "consensus_getReceipts": return rewriteParam(rctx, req, res, 0, true) case "eth_getBalance", "eth_getCode", @@ -84,38 +82,6 @@ func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResul return RewriteNone, nil } -func rewriteGetReceiptsParams(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { - var p []interface{} - err := json.Unmarshal(req.Params, &p) - if err != nil { - return RewriteOverrideError, err - } - - if len(p) != 1 { - return RewriteNone, nil - } - - if m, ok := p[0].(map[string]interface{}); !ok || m["blockOrHash"] == nil { - return RewriteNone, nil - } - - val, rw, err := rewriteTag(rctx, p[0].(map[string]interface{})["blockOrHash"].(string)) - if err != nil { - return RewriteOverrideError, err - } - - if rw { - p[0].(map[string]interface{})["blockOrHash"] = val - paramRaw, err := json.Marshal(p) - if err != nil { - return RewriteOverrideError, err - } - req.Params = paramRaw - return RewriteOverrideRequest, nil - } - return RewriteNone, nil -} - func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool) (RewriteResult, error) { var p []interface{} err := json.Unmarshal(req.Params, &p) From 3b56ec9d9edd81827d8a6afc3d0bc3245c183139 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 2 Jun 2023 14:11:49 -0700 Subject: [PATCH 133/212] fix targets --- proxyd/proxyd/proxyd.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 4a523f6..f1e9275 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -328,12 +328,13 @@ func Start(config *Config) (*Server, func(), error) { func validateReceiptsTarget(val string) (string, error) { if val == "" { - val = "debug_getRawReceipts" + val = ReceiptsTargetDebugGetRawReceipts } switch val { - case "debug_getRawReceipts", - "eth_getTransactionReceipt", - "alchemy_getTransactionReceipts": + case ReceiptsTargetDebugGetRawReceipts, + ReceiptsTargetAlchemyGetTransactionReceipts, + ReceiptsTargetEthGetTransactionReceipts, + ReceiptsTargetParityGetTransactionReceipts: return val, nil default: return "", fmt.Errorf("invalid receipts target: %s", val) From d8ad7900d92faaf6c3df606271d04145ec14125a Mon Sep 17 00:00:00 2001 From: Francis Li Date: Wed, 21 Jun 2023 11:06:49 -0700 Subject: [PATCH 134/212] [proxyd] do not block auth rpc call when auth is not enabled --- proxyd/proxyd/server.go | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 88b031c..847ad51 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -16,11 +16,9 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" "github.com/go-redis/redis/v8" "github.com/gorilla/mux" @@ -580,16 +578,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context } ctx := context.WithValue(r.Context(), ContextKeyXForwardedFor, xff) // nolint:staticcheck - if len(s.authenticatedPaths) == 0 { - // handle the edge case where auth is disabled - // but someone sends in an auth key anyway - if authorization != "" { - log.Info("blocked authenticated request against unauthenticated proxy") - httpResponseCodesTotal.WithLabelValues("404").Inc() - w.WriteHeader(404) - return nil - } - } else { + if len(s.authenticatedPaths) > 0 { if authorization == "" || s.authenticatedPaths[authorization] == "" { log.Info("blocked unauthorized request", "authorization", authorization) httpResponseCodesTotal.WithLabelValues("401").Inc() From 16b03049aef330f3e54ebcb9d3d84f5bc8b9c659 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Wed, 19 Jul 2023 13:21:00 -0700 Subject: [PATCH 135/212] feat(proxyd): sender rate limiter should check chain id --- proxyd/proxyd/config.go | 8 ++++--- .../sender_rate_limit_test.go | 14 +++++------ .../testdata/sender_rate_limit.toml | 3 ++- .../integration_tests/testdata/testdata.txt | 4 +++- proxyd/proxyd/server.go | 23 +++++++++++++++++++ 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index bd58f62..978506f 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -2,6 +2,7 @@ package proxyd import ( "fmt" + "math/big" "os" "strings" "time" @@ -121,9 +122,10 @@ type BatchConfig struct { // SenderRateLimitConfig configures the sender-based rate limiter // for eth_sendRawTransaction requests. type SenderRateLimitConfig struct { - Enabled bool - Interval TOMLDuration - Limit int + Enabled bool + Interval TOMLDuration + Limit int + AllowedChainIds []*big.Int `toml:"allowed_chain_ids"` } type Config struct { diff --git a/proxyd/proxyd/integration_tests/sender_rate_limit_test.go b/proxyd/proxyd/integration_tests/sender_rate_limit_test.go index a31a077..20c5f0a 100644 --- a/proxyd/proxyd/integration_tests/sender_rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/sender_rate_limit_test.go @@ -20,12 +20,10 @@ const txHex1 = "0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d "1145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee" + "08bfac58f58fb3b8bcef5af98578bdeaddf40bde42" -const txHex2 = "0xf8aa82afd2830f4240830493e094464959ad46e64046b891f562cff202a465d5" + - "22f380b844d5bade070000000000000000000000004200000000000000000000" + - "0000000000000000060000000000000000000000000000000000000000000000" + - "0000000025ef43fc0038a05d8ea9837ea81469bda4dadbe852fdd37fcfbcd666" + - "5641a35e4726fbc04364e7a0107e20bb34aea53c695a551204a11d42fe465055" + - "510ee240e8f884fb70289be6" +const txHex2 = "0x02f8758201a48217fd84773594008504a817c80082520894be53e587975603" + + "a13d0923d0aa6d37c5233dd750865af3107a400080c080a04aefbd5819c35729" + + "138fe26b6ae1783ebf08d249b356c2f920345db97877f3f7a008d5ae92560a3c" + + "65f723439887205713af7ce7d7f6b24fba198f2afa03435867" const dummyRes = `{"id": 123, "jsonrpc": "2.0", "result": "dummy"}` @@ -81,10 +79,10 @@ func TestSenderRateLimitLimiting(t *testing.T) { // should be rate limited. res1, code1, err := client.SendRequest(makeSendRawTransaction(txHex1)) require.NoError(t, err) - res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1)) - require.NoError(t, err) RequireEqualJSON(t, []byte(dummyRes), res1) require.Equal(t, 200, code1) + res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1)) + require.NoError(t, err) RequireEqualJSON(t, []byte(limRes), res2) require.Equal(t, 429, code2) diff --git a/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml index 840c295..024858a 100644 --- a/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml @@ -20,4 +20,5 @@ eth_sendRawTransaction = "main" [sender_rate_limit] enabled = true interval = "1s" -limit = 1 \ No newline at end of file +limit = 1 +allowed_chain_ids = [420] diff --git a/proxyd/proxyd/integration_tests/testdata/testdata.txt b/proxyd/proxyd/integration_tests/testdata/testdata.txt index 49d0a87..7e31927 100644 --- a/proxyd/proxyd/integration_tests/testdata/testdata.txt +++ b/proxyd/proxyd/integration_tests/testdata/testdata.txt @@ -8,4 +8,6 @@ invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","par invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x1234"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"transaction type not supported"},"id":1} valid transaction data - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} valid transaction data - contract call|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6cd17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1b2774201bac50f627403eac1b732459cf70000000000000000000000000000000000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd61145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} -batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":123}] \ No newline at end of file +valid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} +invalid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1} +batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1},{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1},{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":123}] diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 847ad51..efe7c5f 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "math" + "math/big" "net/http" "regexp" "strconv" @@ -18,6 +19,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "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/log" "github.com/go-redis/redis/v8" @@ -56,6 +58,7 @@ type Server struct { mainLim FrontendRateLimiter overrideLims map[string]FrontendRateLimiter senderLim FrontendRateLimiter + allowedChainIds []*big.Int limExemptOrigins []*regexp.Regexp limExemptUserAgents []*regexp.Regexp globallyLimitedMethods map[string]bool @@ -173,6 +176,7 @@ func NewServer( overrideLims: overrideLims, globallyLimitedMethods: globalMethodLims, senderLim: senderLim, + allowedChainIds: senderRateLimitConfig.AllowedChainIds, limExemptOrigins: limExemptOrigins, limExemptUserAgents: limExemptUserAgents, }, nil @@ -654,6 +658,13 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { return ErrInvalidParams(err.Error()) } + // Check if the transaction is for the expected chain, + // otherwise reject before rate limiting to avoid replay attacks. + if !s.isAllowedChainId(tx.ChainId()) { + log.Debug("chain id is not allowed", "req_id", GetReqID(ctx)) + return txpool.ErrInvalidSender + } + // Convert the transaction into a Message object so that we can get the // sender. This method performs an ecrecover, which can be expensive. msg, err := core.TransactionToMessage(tx, types.LatestSignerForChainID(tx.ChainId()), nil) @@ -674,6 +685,18 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { return nil } +func (s *Server) isAllowedChainId(chainId *big.Int) bool { + if s.allowedChainIds == nil || len(s.allowedChainIds) == 0 { + return true + } + for _, id := range s.allowedChainIds { + if chainId.Cmp(id) == 0 { + return true + } + } + return false +} + func setCacheHeader(w http.ResponseWriter, cached bool) { if cached { w.Header().Set(cacheStatusHdr, "HIT") From a65810b467ee0ca6c26a1bed7964ee856bfad0d4 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 27 Jul 2023 11:48:46 -0700 Subject: [PATCH 136/212] feat(proxyd): betterer timeoutz --- proxyd/proxyd/backend.go | 54 ++++++++++++++++++++++++++++++++-------- proxyd/proxyd/server.go | 14 ++++++++--- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 9c4234f..4314848 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -854,9 +854,12 @@ func calcBackoff(i int) time.Duration { type WSProxier struct { backend *Backend clientConn *websocket.Conn - backendConn *websocket.Conn - methodWhitelist *StringSet clientConnMu sync.Mutex + backendConn *websocket.Conn + backendConnMu sync.Mutex + methodWhitelist *StringSet + readTimeout time.Duration + writeTimeout time.Duration } func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, methodWhitelist *StringSet) *WSProxier { @@ -865,6 +868,8 @@ func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, met clientConn: clientConn, backendConn: backendConn, methodWhitelist: methodWhitelist, + readTimeout: defaultWSReadTimeout, + writeTimeout: defaultWSWriteTimeout, } } @@ -879,14 +884,21 @@ func (w *WSProxier) Proxy(ctx context.Context) error { func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { for { + err := w.clientConn.SetReadDeadline(time.Now().Add(w.readTimeout)) + if err != nil { + log.Error("ws client read timeout", "err", err) + errC <- err + return + } + // Block until we get a message. msgType, msg, err := w.clientConn.ReadMessage() if err != nil { - errC <- err - if err := w.backendConn.WriteMessage(websocket.CloseMessage, formatWSError(err)); err != nil { + if err := w.writeBackendConn(websocket.CloseMessage, formatWSError(err)); err != nil { log.Error("error writing backendConn message", "err", err) + errC <- err + return } - return } RecordWSMessage(ctx, w.backend.Name, SourceClient) @@ -894,7 +906,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { // Route control messages to the backend. These don't // count towards the total RPC requests count. if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage { - err := w.backendConn.WriteMessage(msgType, msg) + err := w.writeBackendConn(msgType, msg) if err != nil { errC <- err return @@ -952,7 +964,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { "req_id", GetReqID(ctx), ) - err = w.backendConn.WriteMessage(msgType, msg) + err = w.writeBackendConn(msgType, msg) if err != nil { errC <- err return @@ -962,14 +974,21 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { for { + err := w.backendConn.SetReadDeadline(time.Now().Add(w.readTimeout)) + if err != nil { + log.Error("ws backend read timeout", "err", err) + errC <- err + return + } + // Block until we get a message. msgType, msg, err := w.backendConn.ReadMessage() if err != nil { - errC <- err if err := w.writeClientConn(websocket.CloseMessage, formatWSError(err)); err != nil { log.Error("error writing clientConn message", "err", err) + errC <- err + return } - return } RecordWSMessage(ctx, w.backend.Name, SourceBackend) @@ -1050,8 +1069,23 @@ func (w *WSProxier) parseBackendMsg(msg []byte) (*RPCRes, error) { func (w *WSProxier) writeClientConn(msgType int, msg []byte) error { w.clientConnMu.Lock() + defer w.clientConnMu.Unlock() + if err := w.clientConn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); err != nil { + log.Error("ws client write timeout", "err", err) + return err + } err := w.clientConn.WriteMessage(msgType, msg) - w.clientConnMu.Unlock() + return err +} + +func (w *WSProxier) writeBackendConn(msgType int, msg []byte) error { + w.backendConnMu.Lock() + defer w.backendConnMu.Unlock() + if err := w.backendConn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); err != nil { + log.Error("ws backend write timeout", "err", err) + return err + } + err := w.writeBackendConn(msgType, msg) return err } diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index efe7c5f..280dc30 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -27,6 +27,7 @@ import ( "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus" "github.com/rs/cors" + "github.com/syndtr/goleveldb/leveldb/opt" ) const ( @@ -35,7 +36,11 @@ const ( ContextKeyXForwardedFor = "x_forwarded_for" MaxBatchRPCCallsHardLimit = 100 cacheStatusHdr = "X-Proxyd-Cache-Status" - defaultServerTimeout = time.Second * 10 + defaultRPCTimeout = 10 * time.Second + defaultBodySizeLimit = 256 * opt.KiB + defaultWSHandshakeTimeout = 10 * time.Second + defaultWSReadTimeout = 2 * time.Minute + defaultWSWriteTimeout = 10 * time.Second maxRequestBodyLogLen = 2000 defaultMaxUpstreamBatchSize = 10 ) @@ -92,11 +97,11 @@ func NewServer( } if maxBodySize == 0 { - maxBodySize = math.MaxInt64 + maxBodySize = defaultBodySizeLimit } if timeout == 0 { - timeout = defaultServerTimeout + timeout = defaultRPCTimeout } if maxUpstreamBatchSize == 0 { @@ -170,7 +175,7 @@ func NewServer( maxRequestBodyLogLen: maxRequestBodyLogLen, maxBatchSize: maxBatchSize, upgrader: &websocket.Upgrader{ - HandshakeTimeout: 5 * time.Second, + HandshakeTimeout: defaultWSHandshakeTimeout, }, mainLim: mainLim, overrideLims: overrideLims, @@ -547,6 +552,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { log.Error("error upgrading client conn", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) return } + clientConn.SetReadLimit(s.maxBodySize) proxier, err := s.wsBackendGroup.ProxyWS(ctx, clientConn, s.wsMethodWhitelist) if err != nil { From 3f48703f265a36de5df80b1c94871f56704480a5 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 27 Jul 2023 13:29:44 -0700 Subject: [PATCH 137/212] testz --- proxyd/proxyd/backend.go | 16 +------ proxyd/proxyd/integration_tests/ws_test.go | 51 ++++++++++++++++++++-- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 4314848..99f8b06 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -884,13 +884,6 @@ func (w *WSProxier) Proxy(ctx context.Context) error { func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { for { - err := w.clientConn.SetReadDeadline(time.Now().Add(w.readTimeout)) - if err != nil { - log.Error("ws client read timeout", "err", err) - errC <- err - return - } - // Block until we get a message. msgType, msg, err := w.clientConn.ReadMessage() if err != nil { @@ -974,13 +967,6 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { for { - err := w.backendConn.SetReadDeadline(time.Now().Add(w.readTimeout)) - if err != nil { - log.Error("ws backend read timeout", "err", err) - errC <- err - return - } - // Block until we get a message. msgType, msg, err := w.backendConn.ReadMessage() if err != nil { @@ -1085,7 +1071,7 @@ func (w *WSProxier) writeBackendConn(msgType int, msg []byte) error { log.Error("ws backend write timeout", "err", err) return err } - err := w.writeBackendConn(msgType, msg) + err := w.backendConn.WriteMessage(msgType, msg) return err } diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go index c0907fe..fdaae0b 100644 --- a/proxyd/proxyd/integration_tests/ws_test.go +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -2,16 +2,17 @@ package integration_tests import ( "os" + "strings" "sync" "sync/atomic" "testing" "time" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum-optimism/optimism/proxyd" + "github.com/ethereum/go-ethereum/log" "github.com/gorilla/websocket" "github.com/stretchr/testify/require" + "github.com/syndtr/goleveldb/leveldb/opt" ) // TestConcurrentWSPanic tests for a panic in the websocket proxy @@ -201,7 +202,7 @@ func TestWS(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - timeout := time.NewTicker(30 * time.Second) + timeout := time.NewTicker(10 * time.Second) doneCh := make(chan struct{}, 1) backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) { require.NoError(t, conn.WriteMessage(websocket.TextMessage, []byte(tt.backendRes))) @@ -270,3 +271,47 @@ func TestWSClientClosure(t *testing.T) { }) } } + +func TestWSClientExceedReadLimit(t *testing.T) { + backendHdlr := new(backendHandler) + clientHdlr := new(clientHandler) + + backend := NewMockWSBackend(nil, func(conn *websocket.Conn, msgType int, data []byte) { + backendHdlr.MsgCB(conn, msgType, data) + }, func(conn *websocket.Conn, err error) { + backendHdlr.CloseCB(conn, err) + }) + defer backend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) + + config := ReadConfig("ws") + _, shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { + clientHdlr.MsgCB(msgType, data) + }, nil) + require.NoError(t, err) + + closed := false + originalHandler := client.conn.CloseHandler() + client.conn.SetCloseHandler(func(code int, text string) error { + closed = true + return originalHandler(code, text) + }) + + backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) { + t.Fatalf("backend should not get the large message") + }) + + clientReq := "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"" + strings.Repeat("barf", 256*opt.KiB+1) + "\"]}" + err = client.WriteMessage( + websocket.TextMessage, + []byte(clientReq), + ) + require.Error(t, err) + require.True(t, closed) + +} From 6809ceb44f55b4b03779e9129169abb8f2b9a163 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 27 Jul 2023 14:16:09 -0700 Subject: [PATCH 138/212] fix test --- proxyd/proxyd/integration_tests/ws_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go index fdaae0b..bd41991 100644 --- a/proxyd/proxyd/integration_tests/ws_test.go +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -12,7 +12,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/gorilla/websocket" "github.com/stretchr/testify/require" - "github.com/syndtr/goleveldb/leveldb/opt" ) // TestConcurrentWSPanic tests for a panic in the websocket proxy @@ -306,7 +305,8 @@ func TestWSClientExceedReadLimit(t *testing.T) { t.Fatalf("backend should not get the large message") }) - clientReq := "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"" + strings.Repeat("barf", 256*opt.KiB+1) + "\"]}" + payload := strings.Repeat("barf", 1024*1024) + clientReq := "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"" + payload + "\"]}" err = client.WriteMessage( websocket.TextMessage, []byte(clientReq), From c24b77cbf8a567e4e8715bca7cd520323339e1a0 Mon Sep 17 00:00:00 2001 From: Michael de Hoog Date: Fri, 18 Aug 2023 13:38:03 -1000 Subject: [PATCH 139/212] proxyd: add support for limiting max block range --- proxyd/proxyd/backend.go | 24 +++++++++---- proxyd/proxyd/config.go | 2 ++ proxyd/proxyd/consensus_poller.go | 11 ++++++ proxyd/proxyd/go.mod | 10 ++++-- proxyd/proxyd/go.sum | 29 +++++++++++++++ proxyd/proxyd/proxyd.go | 4 +++ proxyd/proxyd/rewriter.go | 46 ++++++++++++++++++++++-- proxyd/proxyd/rewriter_test.go | 60 +++++++++++++++++++++++++++++-- 8 files changed, 173 insertions(+), 13 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 99f8b06..24b2897 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -17,14 +17,14 @@ import ( "sync" "time" - sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sync/semaphore" + + sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" ) const ( @@ -138,6 +138,7 @@ type Backend struct { proxydIP string skipPeerCountCheck bool + forcedCandidate bool maxDegradedLatencyThreshold time.Duration maxLatencyThreshold time.Duration @@ -220,6 +221,12 @@ func WithConsensusSkipPeerCountCheck(skipPeerCountCheck bool) BackendOpt { } } +func WithConsensusForcedCandidate(forcedCandidate bool) BackendOpt { + return func(b *Backend) { + b.forcedCandidate = forcedCandidate + } +} + func WithMaxDegradedLatencyThreshold(maxDegradedLatencyThreshold time.Duration) BackendOpt { return func(b *Backend) { b.maxDegradedLatencyThreshold = maxDegradedLatencyThreshold @@ -675,9 +682,10 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch // We also rewrite block tags to enforce compliance with consensus rctx := RewriteContext{ - latest: bg.Consensus.GetLatestBlockNumber(), - safe: bg.Consensus.GetSafeBlockNumber(), - finalized: bg.Consensus.GetFinalizedBlockNumber(), + latest: bg.Consensus.GetLatestBlockNumber(), + safe: bg.Consensus.GetSafeBlockNumber(), + finalized: bg.Consensus.GetFinalizedBlockNumber(), + maxBlockRange: bg.Consensus.maxBlockRange, } for i, req := range rpcReqs { @@ -692,6 +700,10 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch }) if errors.Is(err, ErrRewriteBlockOutOfRange) { res.Error = ErrBlockOutOfRange + } else if errors.Is(err, ErrRewriteRangeTooLarge) { + res.Error = ErrInvalidParams( + fmt.Sprintf("block range greater than %d max", rctx.maxBlockRange), + ) } else { res.Error = ErrParseErr } diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 978506f..36de1a7 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -93,6 +93,7 @@ type BackendConfig struct { StripTrailingXFF bool `toml:"strip_trailing_xff"` ConsensusSkipPeerCountCheck bool `toml:"consensus_skip_peer_count"` + ConsensusForcedCandidate bool `toml:"consensus_forced_candidate"` ConsensusReceiptsTarget string `toml:"consensus_receipts_target"` } @@ -107,6 +108,7 @@ type BackendGroupConfig struct { ConsensusBanPeriod TOMLDuration `toml:"consensus_ban_period"` ConsensusMaxUpdateThreshold TOMLDuration `toml:"consensus_max_update_threshold"` ConsensusMaxBlockLag uint64 `toml:"consensus_max_block_lag"` + ConsensusMaxBlockRange uint64 `toml:"consensus_max_block_range"` ConsensusMinPeerCount int `toml:"consensus_min_peer_count"` } diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 5944fa7..76a0da3 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -38,6 +38,7 @@ type ConsensusPoller struct { banPeriod time.Duration maxUpdateThreshold time.Duration maxBlockLag uint64 + maxBlockRange uint64 } type backendState struct { @@ -201,6 +202,12 @@ func WithMaxBlockLag(maxBlockLag uint64) ConsensusOpt { } } +func WithMaxBlockRange(maxBlockRange uint64) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.maxBlockRange = maxBlockRange + } +} + func WithMinPeerCount(minPeerCount uint64) ConsensusOpt { return func(cp *ConsensusPoller) { cp.minPeerCount = minPeerCount @@ -621,6 +628,10 @@ func (cp *ConsensusPoller) getConsensusCandidates() map[*Backend]*backendState { for _, be := range cp.backendGroup.Backends { bs := cp.getBackendState(be) + if be.forcedCandidate { + candidates[be] = bs + continue + } if bs.IsBanned() { continue } diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 605a887..fec229b 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -9,7 +9,6 @@ require ( github.com/ethereum/go-ethereum v1.12.0 github.com/go-redis/redis/v8 v8.11.4 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb - github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d @@ -17,6 +16,7 @@ require ( github.com/prometheus/client_golang v1.14.0 github.com/rs/cors v1.8.2 github.com/stretchr/testify v1.8.1 + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 golang.org/x/sync v0.1.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -37,6 +37,8 @@ require ( github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -44,8 +46,11 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/gomodule/redigo v1.8.8 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect + github.com/huin/goupnp v1.0.3 // indirect + github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -59,9 +64,10 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/status-im/keycard-go v0.2.0 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect + github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/crypto v0.1.0 // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index a6edcee..3a5ada6 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -39,6 +39,7 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/xxhash/v2 v2.1.2/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= @@ -62,7 +63,9 @@ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcju github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -92,10 +95,14 @@ github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2 github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= @@ -123,7 +130,9 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 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= @@ -165,6 +174,7 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -174,6 +184,9 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= +github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -182,6 +195,8 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +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/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= @@ -217,11 +232,13 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -232,6 +249,8 @@ github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -285,7 +304,9 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -300,6 +321,8 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +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= @@ -318,10 +341,13 @@ github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03O github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= +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/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= @@ -332,6 +358,7 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= @@ -432,6 +459,7 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -498,6 +526,7 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index f1e9275..15bea43 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -142,6 +142,7 @@ func Start(config *Config) (*Server, func(), error) { } opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP"))) opts = append(opts, WithConsensusSkipPeerCountCheck(cfg.ConsensusSkipPeerCountCheck)) + opts = append(opts, WithConsensusForcedCandidate(cfg.ConsensusForcedCandidate)) receiptsTarget, err := ReadFromEnvOrConfig(cfg.ConsensusReceiptsTarget) if err != nil { @@ -308,6 +309,9 @@ func Start(config *Config) (*Server, func(), error) { if bgcfg.ConsensusMinPeerCount > 0 { copts = append(copts, WithMinPeerCount(uint64(bgcfg.ConsensusMinPeerCount))) } + if bgcfg.ConsensusMaxBlockRange > 0 { + copts = append(copts, WithMaxBlockRange(bgcfg.ConsensusMaxBlockRange)) + } cp := NewConsensusPoller(bg, copts...) bg.Consensus = cp diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index b00bc51..b2d5983 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -9,9 +9,10 @@ import ( ) type RewriteContext struct { - latest hexutil.Uint64 - safe hexutil.Uint64 - finalized hexutil.Uint64 + latest hexutil.Uint64 + safe hexutil.Uint64 + finalized hexutil.Uint64 + maxBlockRange uint64 } type RewriteResult uint8 @@ -32,6 +33,7 @@ const ( var ( ErrRewriteBlockOutOfRange = errors.New("block is out of range") + ErrRewriteRangeTooLarge = errors.New("block range is too large") ) // RewriteTags modifies the request and the response based on block tags @@ -121,6 +123,15 @@ func rewriteRange(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (Rewri return RewriteOverrideError, err } + // if either fromBlock or toBlock is defined, default the other to "latest" if unset + _, hasFrom := p[pos]["fromBlock"] + _, hasTo := p[pos]["toBlock"] + if hasFrom && !hasTo { + p[pos]["toBlock"] = "latest" + } else if hasTo && !hasFrom { + p[pos]["fromBlock"] = "latest" + } + modifiedFrom, err := rewriteTagMap(rctx, p[pos], "fromBlock") if err != nil { return RewriteOverrideError, err @@ -131,6 +142,20 @@ func rewriteRange(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (Rewri return RewriteOverrideError, err } + if rctx.maxBlockRange > 0 && (hasFrom || hasTo) { + from, err := blockNumber(p[pos], "fromBlock", uint64(rctx.latest)) + if err != nil { + return RewriteOverrideError, err + } + to, err := blockNumber(p[pos], "toBlock", uint64(rctx.latest)) + if err != nil { + return RewriteOverrideError, err + } + if to-from > rctx.maxBlockRange { + return RewriteOverrideError, ErrRewriteRangeTooLarge + } + } + // if any of the fields the request have been changed, re-marshal the params if modifiedFrom || modifiedTo { paramsRaw, err := json.Marshal(p) @@ -144,6 +169,21 @@ func rewriteRange(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (Rewri return RewriteNone, nil } +func blockNumber(m map[string]interface{}, key string, latest uint64) (uint64, error) { + current, ok := m[key].(string) + if !ok { + return 0, errors.New("expected string") + } + // the latest/safe/finalized tags are already replaced by rewriteTag + if current == "earliest" { + return 0, nil + } + if current == "pending" { + return latest + 1, nil + } + return hexutil.DecodeUint64(current) +} + func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (bool, error) { if m[key] == nil || m[key] == "" { return false, nil diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go index 0637262..94bc5c9 100644 --- a/proxyd/proxyd/rewriter_test.go +++ b/proxyd/proxyd/rewriter_test.go @@ -48,7 +48,7 @@ func TestRewriteRequest(t *testing.T) { req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(55).String()}})}, res: nil, }, - expected: RewriteNone, + expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { var p []map[string]interface{} err := json.Unmarshal(args.req.Params, &p) @@ -88,7 +88,7 @@ func TestRewriteRequest(t *testing.T) { req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": hexutil.Uint64(55).String()}})}, res: nil, }, - expected: RewriteNone, + expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { var p []map[string]interface{} err := json.Unmarshal(args.req.Params, &p) @@ -148,6 +148,62 @@ func TestRewriteRequest(t *testing.T) { expected: RewriteOverrideError, expectedErr: ErrRewriteBlockOutOfRange, }, + { + name: "eth_getLogs fromBlock -> toBlock above max range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(20).String(), "toBlock": hexutil.Uint64(80).String()}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteRangeTooLarge, + }, + { + name: "eth_getLogs earliest -> latest above max range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "earliest", "toBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteRangeTooLarge, + }, + { + name: "eth_getLogs earliest -> pending above max range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "earliest", "toBlock": "pending"}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteRangeTooLarge, + }, + { + name: "eth_getLogs earliest -> default above max range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "earliest"}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteRangeTooLarge, + }, + { + name: "eth_getLogs default -> latest within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["fromBlock"]) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["toBlock"]) + }, + }, /* required parameter at pos 0 */ { name: "debug_getRawReceipts latest", From 1387fd2fe0578f9ac98f8a3cf657b31bb6d0361e Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Thu, 24 Aug 2023 02:14:38 +1000 Subject: [PATCH 140/212] Update to golang 1.20.7 and alpine 3.18 (#6969) * Update to golang 1.20.7 and alpine 3.18 * Update go version in go.mod files. * Use crypto/rand.Read instead of math/read.Read * Handle error response --- proxyd/proxyd/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index fec229b..3d45fa7 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -1,6 +1,6 @@ module github.com/ethereum-optimism/optimism/proxyd -go 1.18 +go 1.20 require ( github.com/BurntSushi/toml v1.2.0 From b1f3d12549dd364d03eae8dea3ba2aa2a5ffd810 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Sep 2023 12:28:43 +0000 Subject: [PATCH 141/212] build(deps): bump github.com/ethereum/go-ethereum in /proxyd 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] --- proxyd/proxyd/go.mod | 29 ++++++++-------- proxyd/proxyd/go.sum | 80 +++++++++++++++++++------------------------- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 3d45fa7..40abd77 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/alicebob/miniredis v2.5.0+incompatible github.com/emirpasic/gods v1.18.1 - github.com/ethereum/go-ethereum v1.12.0 + github.com/ethereum/go-ethereum v1.12.1 github.com/go-redis/redis/v8 v8.11.4 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 @@ -17,7 +17,7 @@ require ( github.com/rs/cors v1.8.2 github.com/stretchr/testify v1.8.1 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - golang.org/x/sync v0.1.0 + golang.org/x/sync v0.3.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -26,6 +26,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.9.0 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/btcsuite/btcd v0.22.0-beta // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -33,12 +34,14 @@ require ( github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect github.com/cockroachdb/redact v1.1.3 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.10.0 // indirect + github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect + github.com/ethereum/c-kzg-4844 v0.3.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -46,16 +49,14 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/gomodule/redigo v1.8.8 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect - github.com/huin/goupnp v1.0.3 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/holiman/uint256 v1.2.3 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect @@ -64,16 +65,16 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/status-im/keycard-go v0.2.0 // indirect + github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect - github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/crypto v0.1.0 // indirect - golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.9.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 3a5ada6..ab07bcc 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -23,6 +23,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= 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.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= +github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= @@ -39,7 +41,6 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/xxhash/v2 v2.1.2/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= @@ -60,12 +61,16 @@ github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lg github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +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.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA= +github.com/consensys/gnark-crypto v0.10.0/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= +github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -91,18 +96,16 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= -github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/ethereum/c-kzg-4844 v0.3.0 h1:3Y3hD6l5i0dEYsBL50C+Om644kve3pNqoAcvE26o9zI= +github.com/ethereum/c-kzg-4844 v0.3.0/go.mod h1:WI2Nd82DMZAAZI1wV2neKGost9EKjvbpQR9OqE5Qqa8= +github.com/ethereum/go-ethereum v1.12.1 h1:1kXDPxhLfyySuQYIfRxVBGYuaHdxNNxevA73vjIwsgk= +github.com/ethereum/go-ethereum v1.12.1/go.mod h1:zKetLweqBR8ZS+1O9iJWI8DvmmD2NzD19apjEWDCsnw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= @@ -130,9 +133,7 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 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= @@ -165,28 +166,23 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= +github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= -github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -195,8 +191,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -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/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= @@ -228,17 +222,16 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -249,8 +242,9 @@ github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -304,9 +298,7 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -321,8 +313,6 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -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= @@ -335,19 +325,18 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b h1:u49mjRnygnB34h8OKbnNJFVUtWSKIKb1KukdV8bILUM= +github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= -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/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= @@ -358,7 +347,6 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= @@ -381,11 +369,11 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 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= @@ -415,7 +403,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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= @@ -425,8 +413,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -459,9 +447,8 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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= @@ -469,8 +456,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -526,7 +513,6 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -544,3 +530,5 @@ 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= From f93f2a8960abb8137c16dd7309591b053d5c4759 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Sep 2023 21:22:59 +0000 Subject: [PATCH 142/212] build(deps): bump github.com/supranational/blst in /proxyd 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] --- proxyd/proxyd/go.mod | 2 +- proxyd/proxyd/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 40abd77..7ad4481 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -65,7 +65,7 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b // indirect + github.com/supranational/blst v0.3.11 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index ab07bcc..0eeab33 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -325,8 +325,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b h1:u49mjRnygnB34h8OKbnNJFVUtWSKIKb1KukdV8bILUM= -github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= From 3e6f70c3fc1381ba2effbb4b3b615c676b5f5138 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 14 Sep 2023 12:36:24 -0700 Subject: [PATCH 143/212] feat(proxyd): high availability --- proxyd/proxyd/backend.go | 4 +- proxyd/proxyd/cache.go | 4 +- proxyd/proxyd/config.go | 4 + proxyd/proxyd/consensus_poller.go | 34 +-- proxyd/proxyd/consensus_tracker.go | 236 +++++++++++++++++--- proxyd/proxyd/frontend_rate_limiter.go | 2 +- proxyd/proxyd/frontend_rate_limiter_test.go | 2 +- proxyd/proxyd/go.mod | 7 +- proxyd/proxyd/go.sum | 103 ++++++++- proxyd/proxyd/methods.go | 4 +- proxyd/proxyd/metrics.go | 41 +++- proxyd/proxyd/proxyd.go | 23 +- proxyd/proxyd/redis.go | 2 +- proxyd/proxyd/server.go | 10 +- 14 files changed, 416 insertions(+), 60 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 24b2897..71ab275 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -630,7 +630,7 @@ func (b *Backend) ErrorRate() (errorRate float64) { return errorRate } -// IsDegraded checks if the backend is serving traffic in a degraded state (i.e. used as a last resource) +// IsDegraded checks if the backend is serving traffic in a degraded local (i.e. used as a last resource) func (b *Backend) IsDegraded() bool { avgLatency := time.Duration(b.latencySlidingWindow.Avg()) return avgLatency >= b.maxDegradedLatencyThreshold @@ -677,7 +677,7 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch if bg.Consensus != nil { // When `consensus_aware` is set to `true`, the backend group acts as a load balancer - // serving traffic from any backend that agrees in the consensus group + // serving traffic update any backend that agrees in the consensus group backends = bg.loadBalancedConsensusGroup() // We also rewrite block tags to enforce compliance with consensus diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index 1cb8c38..e60c6bd 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -7,8 +7,8 @@ import ( "time" "github.com/ethereum/go-ethereum/rpc" + "github.com/redis/go-redis/v9" - "github.com/go-redis/redis/v8" "github.com/golang/snappy" lru "github.com/hashicorp/golang-lru" ) @@ -78,7 +78,7 @@ func (c *redisCache) Get(ctx context.Context, key string) (string, error) { func (c *redisCache) Put(ctx context.Context, key string, value string) error { start := time.Now() - err := c.rdb.SetEX(ctx, c.namespaced(key), value, redisTTL).Err() + err := c.rdb.SetEx(ctx, c.namespaced(key), value, redisTTL).Err() redisCacheDurationSumm.WithLabelValues("SETEX").Observe(float64(time.Since(start).Milliseconds())) if err != nil { diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 36de1a7..14e3ece 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -110,6 +110,10 @@ type BackendGroupConfig struct { ConsensusMaxBlockLag uint64 `toml:"consensus_max_block_lag"` ConsensusMaxBlockRange uint64 `toml:"consensus_max_block_range"` ConsensusMinPeerCount int `toml:"consensus_min_peer_count"` + + ConsensusHA bool `toml:"consensus_ha"` + ConsensusHAHeartbeatInterval TOMLDuration `toml:"consensus_ha_heartbeat_interval"` + ConsensusHALockPeriod TOMLDuration `toml:"consensus_ha_lock_period"` } type BackendGroupsConfig map[string]*BackendGroupConfig diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 76a0da3..93b8c76 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -19,10 +19,11 @@ const ( type OnConsensusBroken func() -// ConsensusPoller checks the consensus state for each member of a BackendGroup +// ConsensusPoller checks the consensus local for each member of a BackendGroup // resolves the highest common block for multiple nodes, and reconciles the consensus // in case of block hash divergence to minimize re-orgs type ConsensusPoller struct { + ctx context.Context cancelFunc context.CancelFunc listeners []OnConsensusBroken @@ -220,6 +221,7 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller state := make(map[*Backend]*backendState, len(bg.Backends)) cp := &ConsensusPoller{ + ctx: ctx, cancelFunc: cancelFunc, backendGroup: bg, backendState: state, @@ -248,7 +250,7 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller return cp } -// UpdateBackend refreshes the consensus state of a single backend +// UpdateBackend refreshes the consensus local of a single backend func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { bs := cp.getBackendState(be) RecordConsensusBackendBanned(be, bs.IsBanned()) @@ -258,7 +260,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - // if backend is not healthy state we'll only resume checking it after ban + // if backend is not healthy local we'll only resume checking it after ban if !be.IsHealthy() { log.Warn("backend banned - not healthy", "backend", be.Name) cp.Ban(be) @@ -268,7 +270,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { inSync, err := cp.isInSync(ctx, be) RecordConsensusBackendInSync(be, err == nil && inSync) if err != nil { - log.Warn("error updating backend sync state", "name", be.Name, "err", err) + log.Warn("error updating backend sync local", "name", be.Name, "err", err) } var peerCount uint64 @@ -306,7 +308,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { RecordBackendFinalizedBlock(be, finalizedBlockNumber) if changed { - log.Debug("backend state updated", + log.Debug("backend local updated", "name", be.Name, "peerCount", peerCount, "inSync", inSync, @@ -352,9 +354,9 @@ func (cp *ConsensusPoller) checkExpectedBlockTags( currentSafe <= currentLatest } -// UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends +// UpdateBackendGroupConsensus resolves the current group consensus based on the local of the backends func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - // get the latest block number from the tracker + // get the latest block number update the tracker currentConsensusBlockNumber := cp.GetLatestBlockNumber() // get the candidates for the consensus group @@ -472,7 +474,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { RecordGroupConsensusFilteredCount(cp.backendGroup, len(filteredBackendsNames)) RecordGroupTotalCount(cp.backendGroup, len(cp.backendGroup.Backends)) - log.Debug("group state", + log.Debug("group local", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) @@ -493,13 +495,13 @@ func (cp *ConsensusPoller) Ban(be *Backend) { bs.backendStateMux.Lock() bs.bannedUntil = time.Now().Add(cp.banPeriod) - // when we ban a node, we give it the chance to start from any block when it is back + // when we ban a node, we give it the chance to start update any block when it is back bs.latestBlockNumber = 0 bs.safeBlockNumber = 0 bs.finalizedBlockNumber = 0 } -// Unban removes any bans from the backends +// Unban removes any bans update the backends func (cp *ConsensusPoller) Unban(be *Backend) { bs := cp.backendState[be] defer bs.backendStateMux.Unlock() @@ -514,7 +516,7 @@ func (cp *ConsensusPoller) Reset() { } } -// fetchBlock is a convenient wrapper to make a request to get a block directly from the backend +// fetchBlock is a convenient wrapper to make a request to get a block directly update the backend func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block string) (blockNumber hexutil.Uint64, blockHash string, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_getBlockByNumber", block, false) @@ -532,7 +534,7 @@ func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block st return } -// getPeerCount is a convenient wrapper to retrieve the current peer count from the backend +// getPeerCount is a convenient wrapper to retrieve the current peer count update the backend func (cp *ConsensusPoller) getPeerCount(ctx context.Context, be *Backend) (count uint64, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "net_peerCount") @@ -550,7 +552,7 @@ func (cp *ConsensusPoller) getPeerCount(ctx context.Context, be *Backend) (count return count, nil } -// isInSync is a convenient wrapper to check if the backend is in sync from the network +// isInSync is a convenient wrapper to check if the backend is in sync update the network func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bool, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_syncing") @@ -577,7 +579,7 @@ func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bo return res, nil } -// getBackendState creates a copy of backend state so that the caller can use it without locking +// getBackendState creates a copy of backend local so that the caller can use it without locking func (cp *ConsensusPoller) getBackendState(be *Backend) *backendState { bs := cp.backendState[be] defer bs.backendStateMux.Unlock() @@ -614,7 +616,7 @@ func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync } // getConsensusCandidates find out what backends are the candidates to be in the consensus group -// and create a copy of current their state +// and create a copy of current their local // // a candidate is a serving node within the following conditions: // - not banned @@ -668,7 +670,7 @@ func (cp *ConsensusPoller) getConsensusCandidates() map[*Backend]*backendState { } } - // remove lagging backends from the candidates + // remove lagging backends update the candidates for _, be := range lagging { delete(candidates, be) } diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go index b0bcb45..d86ac9c 100644 --- a/proxyd/proxyd/consensus_tracker.go +++ b/proxyd/proxyd/consensus_tracker.go @@ -2,12 +2,17 @@ package proxyd import ( "context" + "encoding/json" "fmt" + "os" "sync" + "time" "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/go-redis/redis/v8" + "github.com/ethereum/go-ethereum/log" + "github.com/go-redsync/redsync/v4" + "github.com/go-redsync/redsync/v4/redis/goredis/v9" + "github.com/redis/go-redis/v9" ) // ConsensusTracker abstracts how we store and retrieve the current consensus @@ -21,17 +26,29 @@ type ConsensusTracker interface { SetFinalizedBlockNumber(blockNumber hexutil.Uint64) } +// DTO to hold the current consensus state +type ConsensusTrackerState struct { + Latest hexutil.Uint64 `json:"latest"` + Safe hexutil.Uint64 `json:"safe"` + Finalized hexutil.Uint64 `json:"finalized"` +} + +func (s *ConsensusTrackerState) update(o *ConsensusTrackerState) { + s.Latest = o.Latest + s.Safe = o.Safe + s.Finalized = o.Finalized +} + // InMemoryConsensusTracker store and retrieve in memory, async-safe type InMemoryConsensusTracker struct { - latestBlockNumber hexutil.Uint64 - safeBlockNumber hexutil.Uint64 - finalizedBlockNumber hexutil.Uint64 - mutex sync.Mutex + mutex sync.Mutex + state *ConsensusTrackerState } func NewInMemoryConsensusTracker() ConsensusTracker { return &InMemoryConsensusTracker{ mutex: sync.Mutex{}, + state: &ConsensusTrackerState{}, } } @@ -39,83 +56,252 @@ func (ct *InMemoryConsensusTracker) GetLatestBlockNumber() hexutil.Uint64 { defer ct.mutex.Unlock() ct.mutex.Lock() - return ct.latestBlockNumber + return ct.state.Latest } func (ct *InMemoryConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uint64) { defer ct.mutex.Unlock() ct.mutex.Lock() - ct.latestBlockNumber = blockNumber + ct.state.Latest = blockNumber } func (ct *InMemoryConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { defer ct.mutex.Unlock() ct.mutex.Lock() - return ct.safeBlockNumber + return ct.state.Safe } func (ct *InMemoryConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint64) { defer ct.mutex.Unlock() ct.mutex.Lock() - ct.safeBlockNumber = blockNumber + ct.state.Safe = blockNumber } func (ct *InMemoryConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { defer ct.mutex.Unlock() ct.mutex.Lock() - return ct.finalizedBlockNumber + return ct.state.Finalized } func (ct *InMemoryConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { defer ct.mutex.Unlock() ct.mutex.Lock() - ct.finalizedBlockNumber = blockNumber + ct.state.Finalized = blockNumber } -// RedisConsensusTracker uses a Redis `client` to store and retrieve consensus, async-safe +// RedisConsensusTracker store and retrieve in a shared Redis cluster, with leader election type RedisConsensusTracker struct { ctx context.Context client *redis.Client - backendGroup string + namespace string + backendGroup *BackendGroup + + redlock *redsync.Mutex + lockPeriod time.Duration + heartbeatInterval time.Duration + + leader bool + leaderName string + + // holds the state collected by local pollers + local *InMemoryConsensusTracker + + // holds a copy of the remote shared state + // when leader, updates the remote with the local state + remote *InMemoryConsensusTracker } -func NewRedisConsensusTracker(ctx context.Context, r *redis.Client, namespace string) ConsensusTracker { - return &RedisConsensusTracker{ +type RedisConsensusTrackerOpt func(cp *RedisConsensusTracker) + +func WithLockPeriod(lockPeriod time.Duration) RedisConsensusTrackerOpt { + return func(ct *RedisConsensusTracker) { + ct.lockPeriod = lockPeriod + } +} + +func WithHeartbeatInterval(heartbeatInterval time.Duration) RedisConsensusTrackerOpt { + return func(ct *RedisConsensusTracker) { + ct.heartbeatInterval = heartbeatInterval + } +} +func NewRedisConsensusTracker(ctx context.Context, + redisClient *redis.Client, + bg *BackendGroup, + namespace string, + opts ...RedisConsensusTrackerOpt) ConsensusTracker { + + tracker := &RedisConsensusTracker{ ctx: ctx, - client: r, - backendGroup: namespace, + client: redisClient, + backendGroup: bg, + namespace: namespace, + + lockPeriod: 30 * time.Second, + heartbeatInterval: 2 * time.Second, + local: NewInMemoryConsensusTracker().(*InMemoryConsensusTracker), + remote: NewInMemoryConsensusTracker().(*InMemoryConsensusTracker), + } + + for _, opt := range opts { + opt(tracker) + } + + return tracker +} + +func (ct *RedisConsensusTracker) Init() { + go func() { + for { + // follow same context as backend group poller + ctx := ct.backendGroup.Consensus.ctx + timer := time.NewTimer(ct.heartbeatInterval) + ct.stateHeartbeat() + + select { + case <-timer.C: + case <-ctx.Done(): + timer.Stop() + return + } + } + }() +} + +func (ct *RedisConsensusTracker) stateHeartbeat() { + pool := goredis.NewPool(ct.client) + rs := redsync.New(pool) + key := ct.key("mutex") + + val, err := ct.client.Get(ct.ctx, key).Result() + if err != nil && err != redis.Nil { + log.Error("failed to read the mutex", "err", err) + ct.leader = false + return + } + if val != "" { + if ct.leader { + log.Debug("extending lock") + ok, err := ct.redlock.Extend() + if err != nil || !ok { + log.Error("failed to extend lock", "err", err, "mutex", ct.redlock.Name(), "val", ct.redlock.Value()) + ct.leader = false + return + } + ct.postPayload(val) + } else { + // retrieve current leader + leaderName, err := ct.client.Get(ct.ctx, ct.key(fmt.Sprintf("leader:%s", val))).Result() + if err != nil && err != redis.Nil { + log.Error("failed to read the remote leader", "err", err) + return + } + ct.leaderName = leaderName + log.Debug("following", "val", val, "leader", leaderName) + // retrieve payload + val, err := ct.client.Get(ct.ctx, ct.key(fmt.Sprintf("state:%s", val))).Result() + if err != nil && err != redis.Nil { + log.Error("failed to read the remote state", "err", err) + return + } + if val == "" { + log.Error("remote state is missing (recent leader election maybe?)") + return + } + state := &ConsensusTrackerState{} + err = json.Unmarshal([]byte(val), state) + if err != nil { + log.Error("failed to unmarshal the remote state", "err", err) + return + } + ct.remote.mutex.Lock() + defer ct.remote.mutex.Unlock() + ct.remote.state.update(state) + log.Debug("updated state from remote", "state", val, "leader", leaderName) + } + } else { + if ct.local.GetLatestBlockNumber() == 0 || + ct.local.GetSafeBlockNumber() == 0 || + ct.local.GetFinalizedBlockNumber() == 0 { + log.Warn("lock not found, but local state is missing, skipping") + return + } + log.Info("lock not found, creating a new one") + + mutex := rs.NewMutex(key, + redsync.WithExpiry(ct.lockPeriod), + redsync.WithFailFast(true), + redsync.WithTries(1)) + + if err := mutex.Lock(); err != nil { + log.Debug("failed to obtain lock", "err", err) + ct.leader = false + return + } + + log.Info("lock acquired", "mutex", mutex.Name(), "val", mutex.Value()) + ct.redlock = mutex + ct.leader = true + ct.postPayload(mutex.Value()) } } func (ct *RedisConsensusTracker) key(tag string) string { - return fmt.Sprintf("consensus:%s:%s", ct.backendGroup, tag) + return fmt.Sprintf("consensus:%s:%s", ct.namespace, tag) } func (ct *RedisConsensusTracker) GetLatestBlockNumber() hexutil.Uint64 { - return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key("latest")).Val())) + return ct.remote.GetLatestBlockNumber() } func (ct *RedisConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uint64) { - ct.client.Set(ct.ctx, ct.key("latest"), blockNumber, 0) + ct.local.SetLatestBlockNumber(blockNumber) } func (ct *RedisConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { - return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key("safe")).Val())) + return ct.remote.GetSafeBlockNumber() } func (ct *RedisConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint64) { - ct.client.Set(ct.ctx, ct.key("safe"), blockNumber, 0) + ct.local.SetSafeBlockNumber(blockNumber) } func (ct *RedisConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { - return hexutil.Uint64(hexutil.MustDecodeUint64(ct.client.Get(ct.ctx, ct.key("finalized")).Val())) + return ct.remote.GetFinalizedBlockNumber() } func (ct *RedisConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { - ct.client.Set(ct.ctx, ct.key("finalized"), blockNumber, 0) + ct.local.SetFinalizedBlockNumber(blockNumber) +} + +func (ct *RedisConsensusTracker) postPayload(mutexVal string) { + ct.remote.mutex.Lock() + defer ct.remote.mutex.Unlock() + + jsonState, err := json.Marshal(ct.local.state) + if err != nil { + log.Error("failed to marshal local", "err", err) + ct.leader = false + return + } + ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("state:%s", mutexVal)), jsonState, ct.lockPeriod) + + leader, _ := os.LookupEnv("HOSTNAME") + if leader == "" { + + } + ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("leader:%s", mutexVal)), leader, ct.lockPeriod) + + log.Debug("posted state", "state", string(jsonState), "leader", leader) + + ct.leaderName = leader + ct.remote.state.update(ct.local.state) + + RecordGroupConsensusHALatestBlock(ct.backendGroup, leader, ct.local.state.Latest) + RecordGroupConsensusHASafeBlock(ct.backendGroup, leader, ct.local.state.Safe) + RecordGroupConsensusHAFinalizedBlock(ct.backendGroup, leader, ct.local.state.Finalized) } diff --git a/proxyd/proxyd/frontend_rate_limiter.go b/proxyd/proxyd/frontend_rate_limiter.go index d377370..d0590f0 100644 --- a/proxyd/proxyd/frontend_rate_limiter.go +++ b/proxyd/proxyd/frontend_rate_limiter.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" ) type FrontendRateLimiter interface { diff --git a/proxyd/proxyd/frontend_rate_limiter_test.go b/proxyd/proxyd/frontend_rate_limiter_test.go index f3542cf..fb5f808 100644 --- a/proxyd/proxyd/frontend_rate_limiter_test.go +++ b/proxyd/proxyd/frontend_rate_limiter_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/alicebob/miniredis" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" "github.com/stretchr/testify/require" ) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 7ad4481..f7ee8d1 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -7,7 +7,6 @@ require ( github.com/alicebob/miniredis v2.5.0+incompatible github.com/emirpasic/gods v1.18.1 github.com/ethereum/go-ethereum v1.12.1 - github.com/go-redis/redis/v8 v8.11.4 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 @@ -44,11 +43,14 @@ require ( github.com/ethereum/c-kzg-4844 v0.3.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-redsync/redsync/v4 v4.9.4 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/gomodule/redigo v1.8.8 // indirect + github.com/gomodule/redigo v1.8.9 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.3 // indirect github.com/klauspost/compress v1.15.15 // indirect @@ -62,6 +64,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect + github.com/redis/go-redis/v9 v9.1.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 0eeab33..fdfa8c9 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -25,6 +25,8 @@ 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.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= @@ -114,11 +116,17 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= +github.com/go-redsync/redsync/v4 v4.9.4 h1:vRmYusI+qF95XSpApHAdeu+RjyDvxBXbMthbc/x148c= +github.com/go-redsync/redsync/v4 v4.9.4/go.mod h1:RqBDXUw0q+u9FJTeD2gMzGtHeSVV93DiqGl10B9Hn/4= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -136,6 +144,7 @@ github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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= @@ -156,6 +165,8 @@ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= +github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= 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= @@ -163,9 +174,12 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/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.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -174,6 +188,11 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -184,6 +203,7 @@ github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= @@ -257,21 +277,43 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= 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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 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.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +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.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= @@ -290,6 +332,9 @@ github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8u github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= +github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= +github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -298,6 +343,7 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rueian/rueidis v0.0.93/go.mod h1:lo6LBci0D986usi5Wxjb4RVNaWENKYbHZSnufGJ9bTE= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= @@ -325,6 +371,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= @@ -354,10 +401,18 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0= +go.opentelemetry.io/otel/metric v0.35.0/go.mod h1:qAcbhaTRFU6uG8QM7dDo7XvFsWcugziq/5YI065TokQ= +go.opentelemetry.io/otel/sdk v1.12.0/go.mod h1:WYcvtgquYvgODEvxOry5owO2y9MyciW7JqMz6cpXShE= +go.opentelemetry.io/otel/sdk/metric v0.35.0/go.mod h1:eDyp1GxSiwV98kr7w4pzrszQh/eze9MqBqPd2bCPmyE= +go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -369,6 +424,7 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -377,11 +433,16 @@ golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnL 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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -394,6 +455,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/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= @@ -403,6 +465,14 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -413,6 +483,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -427,7 +499,9 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w 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-20191010194322-b09406accb47/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -444,18 +518,37 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/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-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/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.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 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.5/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -472,7 +565,14 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK 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.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= 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= @@ -501,6 +601,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD 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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index ffaf89d..a73d24a 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -44,7 +44,7 @@ func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*R key := e.key(req) val, err := e.cache.Get(ctx, key) if err != nil { - log.Error("error reading from cache", "key", key, "method", req.Method, "err", err) + log.Error("error reading update cache", "key", key, "method", req.Method, "err", err) return nil, err } if val == "" { @@ -53,7 +53,7 @@ func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*R var result interface{} if err := json.Unmarshal([]byte(val), &result); err != nil { - log.Error("error unmarshalling value from cache", "key", key, "method", req.Method, "err", err) + log.Error("error unmarshalling value update cache", "key", key, "method", req.Method, "err", err) return nil, err } return &RPCRes{ diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 0edd820..d1838a1 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -262,6 +262,33 @@ var ( "backend_group_name", }) + consensusHALatestBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_ha_latest_block", + Help: "Consensus HA latest block", + }, []string{ + "backend_group_name", + "leader", + }) + + consensusHASafeBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_ha_safe_block", + Help: "Consensus HA safe block", + }, []string{ + "backend_group_name", + "leader", + }) + + consensusHAFinalizedBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_ha_finalized_block", + Help: "Consensus HA finalized block", + }, []string{ + "backend_group_name", + "leader", + }) + backendLatestBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, Name: "backend_latest_block", @@ -305,7 +332,7 @@ var ( consensusGroupFilteredCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, Name: "group_consensus_filtered_count", - Help: "Consensus group filtered out from serving traffic count", + Help: "Consensus group filtered out update serving traffic count", }, []string{ "backend_group_name", }) @@ -438,6 +465,18 @@ func RecordBatchSize(size int) { batchSizeHistogram.Observe(float64(size)) } +func RecordGroupConsensusHALatestBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { + consensusHALatestBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) +} + +func RecordGroupConsensusHASafeBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { + consensusHASafeBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) +} + +func RecordGroupConsensusHAFinalizedBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { + consensusHAFinalizedBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) +} + func RecordGroupConsensusLatestBlock(group *BackendGroup, blockNumber hexutil.Uint64) { consensusLatestBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) } diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 15bea43..853a38a 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -1,6 +1,7 @@ package proxyd import ( + "context" "crypto/tls" "errors" "fmt" @@ -10,8 +11,8 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/redis/go-redis/v9" "golang.org/x/sync/semaphore" ) @@ -313,8 +314,28 @@ func Start(config *Config) (*Server, func(), error) { copts = append(copts, WithMaxBlockRange(bgcfg.ConsensusMaxBlockRange)) } + var tracker ConsensusTracker + if bgcfg.ConsensusHA { + if redisClient == nil { + log.Crit("cant start - consensus high availability requires redis") + } + topts := make([]RedisConsensusTrackerOpt, 0) + if bgcfg.ConsensusHALockPeriod > 0 { + topts = append(topts, WithLockPeriod(time.Duration(bgcfg.ConsensusHALockPeriod))) + } + if bgcfg.ConsensusHAHeartbeatInterval > 0 { + topts = append(topts, WithLockPeriod(time.Duration(bgcfg.ConsensusHAHeartbeatInterval))) + } + tracker = NewRedisConsensusTracker(context.Background(), redisClient, bg, bg.Name, topts...) + copts = append(copts, WithTracker(tracker)) + } + cp := NewConsensusPoller(bg, copts...) bg.Consensus = cp + + if bgcfg.ConsensusHA { + tracker.(*RedisConsensusTracker).Init() + } } } diff --git a/proxyd/proxyd/redis.go b/proxyd/proxyd/redis.go index e32bff2..bd15f52 100644 --- a/proxyd/proxyd/redis.go +++ b/proxyd/proxyd/redis.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" ) func NewRedisClient(url string) (*redis.Client, error) { diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 280dc30..d14f06f 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -22,10 +22,10 @@ import ( "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus" + "github.com/redis/go-redis/v9" "github.com/rs/cors" "github.com/syndtr/goleveldb/leveldb/opt" ) @@ -653,11 +653,11 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { var data hexutil.Bytes if err := data.UnmarshalText([]byte(params[0])); err != nil { log.Debug("error decoding raw tx data", "err", err, "req_id", GetReqID(ctx)) - // Geth returns the raw error from UnmarshalText. + // Geth returns the raw error update UnmarshalText. return ErrInvalidParams(err.Error()) } - // Inflates a types.Transaction object from the transaction's raw bytes. + // Inflates a types.Transaction object update the transaction's raw bytes. tx := new(types.Transaction) if err := tx.UnmarshalBinary(data); err != nil { log.Debug("could not unmarshal transaction", "err", err, "req_id", GetReqID(ctx)) @@ -675,12 +675,12 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { // sender. This method performs an ecrecover, which can be expensive. msg, err := core.TransactionToMessage(tx, types.LatestSignerForChainID(tx.ChainId()), nil) if err != nil { - log.Debug("could not get message from transaction", "err", err, "req_id", GetReqID(ctx)) + log.Debug("could not get message update transaction", "err", err, "req_id", GetReqID(ctx)) return ErrInvalidParams(err.Error()) } ok, err := s.senderLim.Take(ctx, fmt.Sprintf("%s:%d", msg.From.Hex(), tx.Nonce())) if err != nil { - log.Error("error taking from sender limiter", "err", err, "req_id", GetReqID(ctx)) + log.Error("error taking update sender limiter", "err", err, "req_id", GetReqID(ctx)) return ErrInternal } if !ok { From c200f2f8e383e1ba8558c421f52ea34b189d68e1 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 14 Sep 2023 12:42:33 -0700 Subject: [PATCH 144/212] revert weird comment replaces --- proxyd/proxyd/backend.go | 4 ++-- proxyd/proxyd/consensus_poller.go | 32 +++++++++++++++---------------- proxyd/proxyd/methods.go | 4 ++-- proxyd/proxyd/metrics.go | 2 +- proxyd/proxyd/server.go | 8 ++++---- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 71ab275..24b2897 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -630,7 +630,7 @@ func (b *Backend) ErrorRate() (errorRate float64) { return errorRate } -// IsDegraded checks if the backend is serving traffic in a degraded local (i.e. used as a last resource) +// IsDegraded checks if the backend is serving traffic in a degraded state (i.e. used as a last resource) func (b *Backend) IsDegraded() bool { avgLatency := time.Duration(b.latencySlidingWindow.Avg()) return avgLatency >= b.maxDegradedLatencyThreshold @@ -677,7 +677,7 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch if bg.Consensus != nil { // When `consensus_aware` is set to `true`, the backend group acts as a load balancer - // serving traffic update any backend that agrees in the consensus group + // serving traffic from any backend that agrees in the consensus group backends = bg.loadBalancedConsensusGroup() // We also rewrite block tags to enforce compliance with consensus diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 93b8c76..762cfec 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -19,7 +19,7 @@ const ( type OnConsensusBroken func() -// ConsensusPoller checks the consensus local for each member of a BackendGroup +// ConsensusPoller checks the consensus state for each member of a BackendGroup // resolves the highest common block for multiple nodes, and reconciles the consensus // in case of block hash divergence to minimize re-orgs type ConsensusPoller struct { @@ -250,7 +250,7 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller return cp } -// UpdateBackend refreshes the consensus local of a single backend +// UpdateBackend refreshes the consensus state of a single backend func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { bs := cp.getBackendState(be) RecordConsensusBackendBanned(be, bs.IsBanned()) @@ -260,7 +260,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { return } - // if backend is not healthy local we'll only resume checking it after ban + // if backend is not healthy state we'll only resume checking it after ban if !be.IsHealthy() { log.Warn("backend banned - not healthy", "backend", be.Name) cp.Ban(be) @@ -270,7 +270,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { inSync, err := cp.isInSync(ctx, be) RecordConsensusBackendInSync(be, err == nil && inSync) if err != nil { - log.Warn("error updating backend sync local", "name", be.Name, "err", err) + log.Warn("error updating backend sync state", "name", be.Name, "err", err) } var peerCount uint64 @@ -308,7 +308,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { RecordBackendFinalizedBlock(be, finalizedBlockNumber) if changed { - log.Debug("backend local updated", + log.Debug("backend state updated", "name", be.Name, "peerCount", peerCount, "inSync", inSync, @@ -354,9 +354,9 @@ func (cp *ConsensusPoller) checkExpectedBlockTags( currentSafe <= currentLatest } -// UpdateBackendGroupConsensus resolves the current group consensus based on the local of the backends +// UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - // get the latest block number update the tracker + // get the latest block number from the tracker currentConsensusBlockNumber := cp.GetLatestBlockNumber() // get the candidates for the consensus group @@ -474,7 +474,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { RecordGroupConsensusFilteredCount(cp.backendGroup, len(filteredBackendsNames)) RecordGroupTotalCount(cp.backendGroup, len(cp.backendGroup.Backends)) - log.Debug("group local", + log.Debug("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) @@ -495,13 +495,13 @@ func (cp *ConsensusPoller) Ban(be *Backend) { bs.backendStateMux.Lock() bs.bannedUntil = time.Now().Add(cp.banPeriod) - // when we ban a node, we give it the chance to start update any block when it is back + // when we ban a node, we give it the chance to start from any block when it is back bs.latestBlockNumber = 0 bs.safeBlockNumber = 0 bs.finalizedBlockNumber = 0 } -// Unban removes any bans update the backends +// Unban removes any bans from the backends func (cp *ConsensusPoller) Unban(be *Backend) { bs := cp.backendState[be] defer bs.backendStateMux.Unlock() @@ -516,7 +516,7 @@ func (cp *ConsensusPoller) Reset() { } } -// fetchBlock is a convenient wrapper to make a request to get a block directly update the backend +// fetchBlock is a convenient wrapper to make a request to get a block directly from the backend func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block string) (blockNumber hexutil.Uint64, blockHash string, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_getBlockByNumber", block, false) @@ -534,7 +534,7 @@ func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block st return } -// getPeerCount is a convenient wrapper to retrieve the current peer count update the backend +// getPeerCount is a convenient wrapper to retrieve the current peer count from the backend func (cp *ConsensusPoller) getPeerCount(ctx context.Context, be *Backend) (count uint64, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "net_peerCount") @@ -552,7 +552,7 @@ func (cp *ConsensusPoller) getPeerCount(ctx context.Context, be *Backend) (count return count, nil } -// isInSync is a convenient wrapper to check if the backend is in sync update the network +// isInSync is a convenient wrapper to check if the backend is in sync from the network func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bool, err error) { var rpcRes RPCRes err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_syncing") @@ -579,7 +579,7 @@ func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bo return res, nil } -// getBackendState creates a copy of backend local so that the caller can use it without locking +// getBackendState creates a copy of backend state so that the caller can use it without locking func (cp *ConsensusPoller) getBackendState(be *Backend) *backendState { bs := cp.backendState[be] defer bs.backendStateMux.Unlock() @@ -616,7 +616,7 @@ func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync } // getConsensusCandidates find out what backends are the candidates to be in the consensus group -// and create a copy of current their local +// and create a copy of current their state // // a candidate is a serving node within the following conditions: // - not banned @@ -670,7 +670,7 @@ func (cp *ConsensusPoller) getConsensusCandidates() map[*Backend]*backendState { } } - // remove lagging backends update the candidates + // remove lagging backends from the candidates for _, be := range lagging { delete(candidates, be) } diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index a73d24a..ffaf89d 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -44,7 +44,7 @@ func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*R key := e.key(req) val, err := e.cache.Get(ctx, key) if err != nil { - log.Error("error reading update cache", "key", key, "method", req.Method, "err", err) + log.Error("error reading from cache", "key", key, "method", req.Method, "err", err) return nil, err } if val == "" { @@ -53,7 +53,7 @@ func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*R var result interface{} if err := json.Unmarshal([]byte(val), &result); err != nil { - log.Error("error unmarshalling value update cache", "key", key, "method", req.Method, "err", err) + log.Error("error unmarshalling value from cache", "key", key, "method", req.Method, "err", err) return nil, err } return &RPCRes{ diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index d1838a1..68ca4e8 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -332,7 +332,7 @@ var ( consensusGroupFilteredCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, Name: "group_consensus_filtered_count", - Help: "Consensus group filtered out update serving traffic count", + Help: "Consensus group filtered out from serving traffic count", }, []string{ "backend_group_name", }) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index d14f06f..2c45aa7 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -653,11 +653,11 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { var data hexutil.Bytes if err := data.UnmarshalText([]byte(params[0])); err != nil { log.Debug("error decoding raw tx data", "err", err, "req_id", GetReqID(ctx)) - // Geth returns the raw error update UnmarshalText. + // Geth returns the raw error from UnmarshalText. return ErrInvalidParams(err.Error()) } - // Inflates a types.Transaction object update the transaction's raw bytes. + // Inflates a types.Transaction object from the transaction's raw bytes. tx := new(types.Transaction) if err := tx.UnmarshalBinary(data); err != nil { log.Debug("could not unmarshal transaction", "err", err, "req_id", GetReqID(ctx)) @@ -675,12 +675,12 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { // sender. This method performs an ecrecover, which can be expensive. msg, err := core.TransactionToMessage(tx, types.LatestSignerForChainID(tx.ChainId()), nil) if err != nil { - log.Debug("could not get message update transaction", "err", err, "req_id", GetReqID(ctx)) + log.Debug("could not get message from transaction", "err", err, "req_id", GetReqID(ctx)) return ErrInvalidParams(err.Error()) } ok, err := s.senderLim.Take(ctx, fmt.Sprintf("%s:%d", msg.From.Hex(), tx.Nonce())) if err != nil { - log.Error("error taking update sender limiter", "err", err, "req_id", GetReqID(ctx)) + log.Error("error taking from sender limiter", "err", err, "req_id", GetReqID(ctx)) return ErrInternal } if !ok { From fb7d24ce0ca678a98168b6f8c42325753b31cd79 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 14 Sep 2023 12:47:25 -0700 Subject: [PATCH 145/212] semgrep --- proxyd/proxyd/consensus_tracker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go index d86ac9c..c05551d 100644 --- a/proxyd/proxyd/consensus_tracker.go +++ b/proxyd/proxyd/consensus_tracker.go @@ -237,6 +237,8 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { redsync.WithFailFast(true), redsync.WithTries(1)) + // nosemgrep: missing-unlock-before-return + // this lock is hold indefinitely, and it is extended until the leader dies if err := mutex.Lock(); err != nil { log.Debug("failed to obtain lock", "err", err) ct.leader = false From fa1912f594653efe07e885d3f726c6fff7cf432d Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 14 Sep 2023 15:30:41 -0700 Subject: [PATCH 146/212] release lock on errors, validate if local is behind remote before acquiring the lock --- proxyd/proxyd/consensus_tracker.go | 39 +++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go index c05551d..c0d1834 100644 --- a/proxyd/proxyd/consensus_tracker.go +++ b/proxyd/proxyd/consensus_tracker.go @@ -52,6 +52,18 @@ func NewInMemoryConsensusTracker() ConsensusTracker { } } +func (ct *InMemoryConsensusTracker) Valid() bool { + return ct.GetLatestBlockNumber() > 0 && + ct.GetSafeBlockNumber() > 0 && + ct.GetFinalizedBlockNumber() > 0 +} + +func (ct *InMemoryConsensusTracker) Behind(other *InMemoryConsensusTracker) bool { + return ct.GetLatestBlockNumber() < other.GetLatestBlockNumber() || + ct.GetSafeBlockNumber() < other.GetSafeBlockNumber() || + ct.GetFinalizedBlockNumber() < other.GetFinalizedBlockNumber() +} + func (ct *InMemoryConsensusTracker) GetLatestBlockNumber() hexutil.Uint64 { defer ct.mutex.Unlock() ct.mutex.Lock() @@ -179,8 +191,15 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { val, err := ct.client.Get(ct.ctx, key).Result() if err != nil && err != redis.Nil { - log.Error("failed to read the mutex", "err", err) - ct.leader = false + log.Error("failed to read the lock", "err", err) + if ct.leader { + ok, err := ct.redlock.Unlock() + if err != nil || !ok { + log.Error("failed to release the lock after error", "err", err) + return + } + ct.leader = false + } return } if val != "" { @@ -189,6 +208,11 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { ok, err := ct.redlock.Extend() if err != nil || !ok { log.Error("failed to extend lock", "err", err, "mutex", ct.redlock.Name(), "val", ct.redlock.Value()) + ok, err := ct.redlock.Unlock() + if err != nil || !ok { + log.Error("failed to release the lock after error", "err", err) + return + } ct.leader = false return } @@ -224,12 +248,15 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { log.Debug("updated state from remote", "state", val, "leader", leaderName) } } else { - if ct.local.GetLatestBlockNumber() == 0 || - ct.local.GetSafeBlockNumber() == 0 || - ct.local.GetFinalizedBlockNumber() == 0 { - log.Warn("lock not found, but local state is missing, skipping") + if !ct.local.Valid() { + log.Warn("local state is not valid or behind remote, skipping") return } + if ct.remote.Valid() && ct.local.Behind(ct.remote) { + log.Warn("local state is behind remote, skipping") + return + } + log.Info("lock not found, creating a new one") mutex := rs.NewMutex(key, From 3911deccbdb42694248a9b70c975a1706da247b9 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 15 Sep 2023 11:57:57 -0700 Subject: [PATCH 147/212] nits --- proxyd/proxyd/consensus_tracker.go | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go index c0d1834..e4fc505 100644 --- a/proxyd/proxyd/consensus_tracker.go +++ b/proxyd/proxyd/consensus_tracker.go @@ -33,10 +33,13 @@ type ConsensusTrackerState struct { Finalized hexutil.Uint64 `json:"finalized"` } -func (s *ConsensusTrackerState) update(o *ConsensusTrackerState) { - s.Latest = o.Latest - s.Safe = o.Safe - s.Finalized = o.Finalized +func (ct *InMemoryConsensusTracker) update(o *ConsensusTrackerState) { + ct.mutex.Lock() + defer ct.mutex.Unlock() + + ct.state.Latest = o.Latest + ct.state.Safe = o.Safe + ct.state.Finalized = o.Finalized } // InMemoryConsensusTracker store and retrieve in memory, async-safe @@ -169,14 +172,12 @@ func NewRedisConsensusTracker(ctx context.Context, func (ct *RedisConsensusTracker) Init() { go func() { for { - // follow same context as backend group poller - ctx := ct.backendGroup.Consensus.ctx timer := time.NewTimer(ct.heartbeatInterval) ct.stateHeartbeat() select { case <-timer.C: - case <-ctx.Done(): + case <-ct.ctx.Done(): timer.Stop() return } @@ -242,9 +243,8 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { log.Error("failed to unmarshal the remote state", "err", err) return } - ct.remote.mutex.Lock() - defer ct.remote.mutex.Unlock() - ct.remote.state.update(state) + + ct.remote.update(state) log.Debug("updated state from remote", "state", val, "leader", leaderName) } } else { @@ -320,15 +320,12 @@ func (ct *RedisConsensusTracker) postPayload(mutexVal string) { ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("state:%s", mutexVal)), jsonState, ct.lockPeriod) leader, _ := os.LookupEnv("HOSTNAME") - if leader == "" { - - } ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("leader:%s", mutexVal)), leader, ct.lockPeriod) log.Debug("posted state", "state", string(jsonState), "leader", leader) ct.leaderName = leader - ct.remote.state.update(ct.local.state) + ct.remote.update(ct.local.state) RecordGroupConsensusHALatestBlock(ct.backendGroup, leader, ct.local.state.Latest) RecordGroupConsensusHASafeBlock(ct.backendGroup, leader, ct.local.state.Safe) From 86cf9547e7b256b594fccc7cb6e4b0ad084f190f Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 15 Sep 2023 12:33:28 -0700 Subject: [PATCH 148/212] nits --- proxyd/proxyd/consensus_tracker.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go index e4fc505..158c31b 100644 --- a/proxyd/proxyd/consensus_tracker.go +++ b/proxyd/proxyd/consensus_tracker.go @@ -177,6 +177,7 @@ func (ct *RedisConsensusTracker) Init() { select { case <-timer.C: + continue case <-ct.ctx.Done(): timer.Stop() return @@ -246,6 +247,10 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { ct.remote.update(state) log.Debug("updated state from remote", "state", val, "leader", leaderName) + + RecordGroupConsensusHALatestBlock(ct.backendGroup, leaderName, ct.remote.state.Latest) + RecordGroupConsensusHASafeBlock(ct.backendGroup, leaderName, ct.remote.state.Safe) + RecordGroupConsensusHAFinalizedBlock(ct.backendGroup, leaderName, ct.remote.state.Finalized) } } else { if !ct.local.Valid() { @@ -308,9 +313,6 @@ func (ct *RedisConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uin } func (ct *RedisConsensusTracker) postPayload(mutexVal string) { - ct.remote.mutex.Lock() - defer ct.remote.mutex.Unlock() - jsonState, err := json.Marshal(ct.local.state) if err != nil { log.Error("failed to marshal local", "err", err) @@ -327,7 +329,7 @@ func (ct *RedisConsensusTracker) postPayload(mutexVal string) { ct.leaderName = leader ct.remote.update(ct.local.state) - RecordGroupConsensusHALatestBlock(ct.backendGroup, leader, ct.local.state.Latest) - RecordGroupConsensusHASafeBlock(ct.backendGroup, leader, ct.local.state.Safe) - RecordGroupConsensusHAFinalizedBlock(ct.backendGroup, leader, ct.local.state.Finalized) + RecordGroupConsensusHALatestBlock(ct.backendGroup, leader, ct.remote.state.Latest) + RecordGroupConsensusHASafeBlock(ct.backendGroup, leader, ct.remote.state.Safe) + RecordGroupConsensusHAFinalizedBlock(ct.backendGroup, leader, ct.remote.state.Finalized) } From d527545645ffc8400312e869cf1961fb6d96b9a5 Mon Sep 17 00:00:00 2001 From: Michael de Hoog Date: Mon, 25 Sep 2023 18:12:55 -1000 Subject: [PATCH 149/212] proxyd: avoid banning a backend that is a forced candidate (#7275) * Add consensus configuration option to never ban a backend * Fix panic * Remove additional flag, use `consensus_forced_candidate` instead --- proxyd/proxyd/consensus_poller.go | 8 ++++++-- proxyd/proxyd/rewriter.go | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 762cfec..c72992c 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -261,7 +261,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { } // if backend is not healthy state we'll only resume checking it after ban - if !be.IsHealthy() { + if !be.IsHealthy() && !be.forcedCandidate { log.Warn("backend banned - not healthy", "backend", be.Name) cp.Ban(be) return @@ -327,7 +327,7 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { RecordBackendUnexpectedBlockTags(be, !expectedBlockTags) - if !expectedBlockTags { + if !expectedBlockTags && !be.forcedCandidate { log.Warn("backend banned - unexpected block tags", "backend", be.Name, "oldFinalized", bs.finalizedBlockNumber, @@ -490,6 +490,10 @@ func (cp *ConsensusPoller) IsBanned(be *Backend) bool { // Ban bans a specific backend func (cp *ConsensusPoller) Ban(be *Backend) { + if be.forcedCandidate { + return + } + bs := cp.backendState[be] defer bs.backendStateMux.Unlock() bs.backendStateMux.Lock() diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index b2d5983..98b59ec 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -99,7 +99,11 @@ func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, requir return RewriteNone, nil } - val, rw, err := rewriteTag(rctx, p[pos].(string)) + s, ok := p[pos].(string) + if !ok { + return RewriteOverrideError, errors.New("expected string") + } + val, rw, err := rewriteTag(rctx, s) if err != nil { return RewriteOverrideError, err } From 89065e45bdddf127d172fc0d46b3715cfa93aec4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:10:02 +0000 Subject: [PATCH 150/212] build(deps): bump github.com/consensys/gnark-crypto in /proxyd 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] --- proxyd/proxyd/go.mod | 12 ++++++------ proxyd/proxyd/go.sum | 25 +++++++++++++++---------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index f7ee8d1..e895d1f 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -7,14 +7,16 @@ require ( github.com/alicebob/miniredis v2.5.0+incompatible github.com/emirpasic/gods v1.18.1 github.com/ethereum/go-ethereum v1.12.1 + github.com/go-redsync/redsync/v4 v4.9.4 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.14.0 + github.com/redis/go-redis/v9 v9.1.0 github.com/rs/cors v1.8.2 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 golang.org/x/sync v0.3.0 gopkg.in/yaml.v3 v3.0.1 @@ -34,7 +36,7 @@ require ( github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect github.com/cockroachdb/redact v1.1.3 // indirect github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.10.0 // indirect + github.com/consensys/gnark-crypto v0.12.0 // indirect github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect @@ -43,7 +45,6 @@ require ( github.com/ethereum/c-kzg-4844 v0.3.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-redsync/redsync/v4 v4.9.4 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -64,7 +65,6 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/redis/go-redis/v9 v9.1.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect @@ -73,10 +73,10 @@ require ( github.com/tklauser/numcpus v0.4.0 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/crypto v0.9.0 // indirect + golang.org/x/crypto v0.10.0 // indirect golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect rsc.io/tmplfunc v0.0.3 // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index fdfa8c9..229bcce 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -26,7 +26,9 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= +github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= @@ -65,8 +67,8 @@ github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= 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.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA= -github.com/consensys/gnark-crypto v0.10.0/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU= +github.com/consensys/gnark-crypto v0.12.0 h1:1OnSpOykNkUIBIBJKdhwy2p0JlW5o+Az02ICzZmvvdg= +github.com/consensys/gnark-crypto v0.12.0/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -122,8 +124,11 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= +github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-redsync/redsync/v4 v4.9.4 h1:vRmYusI+qF95XSpApHAdeu+RjyDvxBXbMthbc/x148c= github.com/go-redsync/redsync/v4 v4.9.4/go.mod h1:RqBDXUw0q+u9FJTeD2gMzGtHeSVV93DiqGl10B9Hn/4= @@ -163,8 +168,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW 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/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= -github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -301,7 +304,6 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 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.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= @@ -343,6 +345,7 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rueian/rueidis v0.0.93 h1:cG905akj2+QyHx0x9y4mN0K8vLi6M94QiyoLulXS3l0= github.com/rueian/rueidis v0.0.93/go.mod h1:lo6LBci0D986usi5Wxjb4RVNaWENKYbHZSnufGJ9bTE= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -369,8 +372,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= @@ -425,8 +430,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= @@ -549,8 +554,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From bc6f5d75aab42e4422ca8c4b8a3a2b1f0f9604e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:24:01 +0000 Subject: [PATCH 151/212] build(deps): bump github.com/btcsuite/btcd in /proxyd Bumps [github.com/btcsuite/btcd](https://github.com/btcsuite/btcd) from 0.22.0-beta to 0.23.2. - [Release notes](https://github.com/btcsuite/btcd/releases) - [Changelog](https://github.com/btcsuite/btcd/blob/master/CHANGES) - [Commits](https://github.com/btcsuite/btcd/compare/v0.22.0-beta...v0.23.2) --- updated-dependencies: - dependency-name: github.com/btcsuite/btcd dependency-type: indirect ... Signed-off-by: dependabot[bot] --- proxyd/proxyd/go.mod | 1 - proxyd/proxyd/go.sum | 28 +--------------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index e895d1f..ac36a0c 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -28,7 +28,6 @@ require ( github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect - github.com/btcsuite/btcd v0.22.0-beta // 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.9.1 // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 229bcce..f709e08 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -11,7 +11,6 @@ github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKz github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/VictoriaMetrics/fastcache v1.9.0 h1:oMwsS6c8abz98B7ytAewQ7M1ZN/Im/iwKoE1euaFvhs= github.com/VictoriaMetrics/fastcache v1.9.0/go.mod h1:otoTS3xu+6IzF/qByjqzjp3rTuzM3Qf0ScU1UTj97iU= -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= @@ -29,21 +28,9 @@ github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= -github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= 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/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -76,7 +63,6 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -86,7 +72,6 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -214,9 +199,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -228,7 +210,6 @@ github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= 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/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= @@ -284,7 +265,6 @@ github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNs 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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -299,8 +279,6 @@ github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkA github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 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= @@ -418,14 +396,11 @@ go.opentelemetry.io/otel/sdk v1.12.0/go.mod h1:WYcvtgquYvgODEvxOry5owO2y9MyciW7J go.opentelemetry.io/otel/sdk/metric v0.35.0/go.mod h1:eDyp1GxSiwV98kr7w4pzrszQh/eze9MqBqPd2bCPmyE= go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -448,7 +423,6 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= From 609bb4e4f90862ca0ea0170fd29ff7f5c1146ddc Mon Sep 17 00:00:00 2001 From: felipe andrade <130432649+felipe-op@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:42:40 -0700 Subject: [PATCH 152/212] chore(proxyd): remove -race from make tests (#7573) --- proxyd/proxyd/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/Makefile b/proxyd/proxyd/Makefile index 263dc61..049a23a 100644 --- a/proxyd/proxyd/Makefile +++ b/proxyd/proxyd/Makefile @@ -13,9 +13,9 @@ fmt: .PHONY: fmt test: - go test -race -v ./... + go test -v ./... .PHONY: test lint: go vet ./... -.PHONY: test \ No newline at end of file +.PHONY: test From 9e1c96e9c5f304468e68196ed5ac4bcfd9f10cba Mon Sep 17 00:00:00 2001 From: felipe andrade <130432649+felipe-op@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:55:14 -0700 Subject: [PATCH 153/212] add debug tools to image (#7647) --- proxyd/proxyd/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proxyd/proxyd/Dockerfile b/proxyd/proxyd/Dockerfile index 30b216f..86eb7b1 100644 --- a/proxyd/proxyd/Dockerfile +++ b/proxyd/proxyd/Dockerfile @@ -14,6 +14,8 @@ RUN make proxyd FROM alpine:3.18 +RUN apk add bind-tools jq curl + COPY ./proxyd/entrypoint.sh /bin/entrypoint.sh RUN apk update && \ From 4e0100bbe90660b060c13f25a75fc8405fc63d06 Mon Sep 17 00:00:00 2001 From: felipe andrade <130432649+felipe-op@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:19:13 -0700 Subject: [PATCH 154/212] feat(proxyd): make max batch size hard limit 1000 and default 100 (#7719) * feat: make max batch size hard limit 1000 and default 100 * nit: rename const --- proxyd/proxyd/server.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 2c45aa7..c03d6de 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -31,18 +31,19 @@ import ( ) const ( - ContextKeyAuth = "authorization" - ContextKeyReqID = "req_id" - ContextKeyXForwardedFor = "x_forwarded_for" - MaxBatchRPCCallsHardLimit = 100 - cacheStatusHdr = "X-Proxyd-Cache-Status" - defaultRPCTimeout = 10 * time.Second - defaultBodySizeLimit = 256 * opt.KiB - defaultWSHandshakeTimeout = 10 * time.Second - defaultWSReadTimeout = 2 * time.Minute - defaultWSWriteTimeout = 10 * time.Second - maxRequestBodyLogLen = 2000 - defaultMaxUpstreamBatchSize = 10 + ContextKeyAuth = "authorization" + ContextKeyReqID = "req_id" + ContextKeyXForwardedFor = "x_forwarded_for" + DefaultMaxBatchRPCCallsLimit = 100 + MaxBatchRPCCallsHardLimit = 1000 + cacheStatusHdr = "X-Proxyd-Cache-Status" + defaultRPCTimeout = 10 * time.Second + defaultBodySizeLimit = 256 * opt.KiB + defaultWSHandshakeTimeout = 10 * time.Second + defaultWSReadTimeout = 2 * time.Minute + defaultWSWriteTimeout = 10 * time.Second + maxRequestBodyLogLen = 2000 + defaultMaxUpstreamBatchSize = 10 ) var emptyArrayResponse = json.RawMessage("[]") @@ -108,7 +109,11 @@ func NewServer( maxUpstreamBatchSize = defaultMaxUpstreamBatchSize } - if maxBatchSize == 0 || maxBatchSize > MaxBatchRPCCallsHardLimit { + if maxBatchSize == 0 { + maxBatchSize = DefaultMaxBatchRPCCallsLimit + } + + if maxBatchSize > MaxBatchRPCCallsHardLimit { maxBatchSize = MaxBatchRPCCallsHardLimit } From a7c80bfae96f59296b822a3cdfeb0822c5b270e6 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 19 Oct 2023 10:35:32 -0700 Subject: [PATCH 155/212] feat(proxyd): add debug tools to image --- proxyd/proxyd/Dockerfile | 2 +- proxyd/proxyd/go.mod | 82 ++++++++++++++++++----------------- proxyd/proxyd/go.sum | 92 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 40 deletions(-) diff --git a/proxyd/proxyd/Dockerfile b/proxyd/proxyd/Dockerfile index 86eb7b1..077cf2c 100644 --- a/proxyd/proxyd/Dockerfile +++ b/proxyd/proxyd/Dockerfile @@ -14,7 +14,7 @@ RUN make proxyd FROM alpine:3.18 -RUN apk add bind-tools jq curl +RUN apk add bind-tools jq curl bash git redis COPY ./proxyd/entrypoint.sh /bin/entrypoint.sh diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index ac36a0c..04f6eb7 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -3,80 +3,84 @@ module github.com/ethereum-optimism/optimism/proxyd go 1.20 require ( - github.com/BurntSushi/toml v1.2.0 + github.com/BurntSushi/toml v1.3.2 github.com/alicebob/miniredis v2.5.0+incompatible github.com/emirpasic/gods v1.18.1 - github.com/ethereum/go-ethereum v1.12.1 - github.com/go-redsync/redsync/v4 v4.9.4 + github.com/ethereum/go-ethereum v1.13.4 + github.com/go-redsync/redsync/v4 v4.10.0 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 - github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d + github.com/hashicorp/golang-lru v1.0.2 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.14.0 - github.com/redis/go-redis/v9 v9.1.0 - github.com/rs/cors v1.8.2 - github.com/stretchr/testify v1.8.2 + github.com/prometheus/client_golang v1.17.0 + github.com/redis/go-redis/v9 v9.2.1 + github.com/rs/cors v1.10.1 + github.com/stretchr/testify v1.8.4 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - golang.org/x/sync v0.3.0 + golang.org/x/sync v0.4.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/DataDog/zstd v1.5.2 // indirect - github.com/VictoriaMetrics/fastcache v1.9.0 // indirect + github.com/DataDog/zstd v1.5.5 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.7.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cockroachdb/errors v1.9.1 // 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-20230209160836-829675f94811 // indirect - github.com/cockroachdb/redact v1.1.3 // indirect + github.com/cockroachdb/pebble v0.0.0-20231017151438-5807b5917615 // 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.0 // indirect - github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-kzg-4844 v0.6.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deckarep/golang-set/v2 v2.1.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/deckarep/golang-set/v2 v2.3.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/ethereum/c-kzg-4844 v0.3.0 // indirect - github.com/getsentry/sentry-go v0.18.0 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/ethereum/c-kzg-4844 v0.3.1 // indirect + github.com/getsentry/sentry-go v0.25.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/gomodule/redigo v1.8.9 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.3 // indirect - github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/compress v1.17.1 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.39.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/supranational/blst v0.3.11 // indirect - github.com/tklauser/go-sysconf v0.3.10 // indirect - github.com/tklauser/numcpus v0.4.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect - github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/crypto v0.10.0 // indirect - golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index f709e08..4ea7906 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -3,14 +3,22 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOv github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +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/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/VictoriaMetrics/fastcache v1.9.0 h1:oMwsS6c8abz98B7ytAewQ7M1ZN/Im/iwKoE1euaFvhs= github.com/VictoriaMetrics/fastcache v1.9.0/go.mod h1:otoTS3xu+6IzF/qByjqzjp3rTuzM3Qf0ScU1UTj97iU= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= @@ -24,12 +32,16 @@ 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.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +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/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= 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/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -42,36 +54,53 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= +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-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= 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-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= +github.com/cockroachdb/pebble v0.0.0-20231017151438-5807b5917615 h1:KU3fuJMEQsorXh5QxKQKuEUkD5bef0SYFh9bqxdo2KA= +github.com/cockroachdb/pebble v0.0.0-20231017151438-5807b5917615/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +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/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= 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.0 h1:1OnSpOykNkUIBIBJKdhwy2p0JlW5o+Az02ICzZmvvdg= github.com/consensys/gnark-crypto v0.12.0/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +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/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= +github.com/crate-crypto/go-kzg-4844 v0.6.0 h1:dJLygEZBHjaHq2R6Wq39lxu7mPzN308VHbmDS1OFhX8= +github.com/crate-crypto/go-kzg-4844 v0.6.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/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A= +github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -87,8 +116,12 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/ethereum/c-kzg-4844 v0.3.0 h1:3Y3hD6l5i0dEYsBL50C+Om644kve3pNqoAcvE26o9zI= github.com/ethereum/c-kzg-4844 v0.3.0/go.mod h1:WI2Nd82DMZAAZI1wV2neKGost9EKjvbpQR9OqE5Qqa8= +github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= +github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.12.1 h1:1kXDPxhLfyySuQYIfRxVBGYuaHdxNNxevA73vjIwsgk= github.com/ethereum/go-ethereum v1.12.1/go.mod h1:zKetLweqBR8ZS+1O9iJWI8DvmmD2NzD19apjEWDCsnw= +github.com/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= +github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUtn9mJW7SsIPB0Q= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -98,6 +131,8 @@ github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= 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/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= +github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= @@ -109,6 +144,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 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-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= @@ -117,6 +154,8 @@ github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F4 github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-redsync/redsync/v4 v4.9.4 h1:vRmYusI+qF95XSpApHAdeu+RjyDvxBXbMthbc/x148c= github.com/go-redsync/redsync/v4 v4.9.4/go.mod h1:RqBDXUw0q+u9FJTeD2gMzGtHeSVV93DiqGl10B9Hn/4= +github.com/go-redsync/redsync/v4 v4.10.0 h1:hTeAak4C73mNBQSTq6KCKDFaiIlfC+z5yTTl8fCJuBs= +github.com/go-redsync/redsync/v4 v4.10.0/go.mod h1:ZfayzutkgeBmEmBlUR3j+rF6kN44UUGtEdfzhBFZTPc= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -149,6 +188,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 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= @@ -184,6 +225,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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= @@ -214,6 +257,8 @@ github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= +github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -239,6 +284,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -258,6 +305,7 @@ github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5Vgl github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -268,6 +316,7 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= @@ -281,6 +330,7 @@ github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1L github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +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/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= @@ -305,24 +355,40 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +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.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rueian/rueidis v0.0.93 h1:cG905akj2+QyHx0x9y4mN0K8vLi6M94QiyoLulXS3l0= github.com/rueian/rueidis v0.0.93/go.mod h1:lo6LBci0D986usi5Wxjb4RVNaWENKYbHZSnufGJ9bTE= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -353,6 +419,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= @@ -361,8 +428,12 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +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.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -390,6 +461,8 @@ github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLC github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0= go.opentelemetry.io/otel/metric v0.35.0/go.mod h1:qAcbhaTRFU6uG8QM7dDo7XvFsWcugziq/5YI065TokQ= go.opentelemetry.io/otel/sdk v1.12.0/go.mod h1:WYcvtgquYvgODEvxOry5owO2y9MyciW7JqMz6cpXShE= @@ -407,9 +480,13 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= 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= @@ -423,6 +500,8 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.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= @@ -466,6 +545,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -511,8 +592,13 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.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.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -530,6 +616,8 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -552,6 +640,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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= @@ -583,6 +673,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 42b2f6db615faea834c35ffaf5cd6032724b0e10 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 19 Oct 2023 11:29:24 -0700 Subject: [PATCH 156/212] feat(proxyd): avoid caching debug_getRawReceipts responses with 0 receipts --- proxyd/proxyd/cache.go | 10 +++++++++- proxyd/proxyd/methods.go | 16 +++++++++++----- .../proxyd/tools/mockserver/handler/handler.go | 2 +- proxyd/proxyd/tools/mockserver/node1.yml | 10 +++++++++- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index e60c6bd..a93a393 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -128,7 +128,7 @@ type rpcCache struct { func newRPCCache(cache Cache) RPCCache { staticHandler := &StaticMethodHandler{cache: cache} debugGetRawReceiptsHandler := &StaticMethodHandler{cache: cache, - filter: func(req *RPCReq) bool { + filterGet: func(req *RPCReq) bool { // cache only if the request is for a block hash var p []rpc.BlockNumberOrHash @@ -141,6 +141,14 @@ func newRPCCache(cache Cache) RPCCache { } return p[0].BlockHash != nil }, + filterPut: func(req *RPCReq, res *RPCRes) bool { + // don't cache if response contains 0 receipts + rawReceipts, ok := res.Result.([]interface{}) + if !ok { + return false + } + return len(rawReceipts) > 0 + }, } handlers := map[string]RPCMethodHandler{ "eth_chainId": staticHandler, diff --git a/proxyd/proxyd/methods.go b/proxyd/proxyd/methods.go index ffaf89d..08ea773 100644 --- a/proxyd/proxyd/methods.go +++ b/proxyd/proxyd/methods.go @@ -17,9 +17,10 @@ type RPCMethodHandler interface { } type StaticMethodHandler struct { - cache Cache - m sync.RWMutex - filter func(*RPCReq) bool + cache Cache + m sync.RWMutex + filterGet func(*RPCReq) bool + filterPut func(*RPCReq, *RPCRes) bool } func (e *StaticMethodHandler) key(req *RPCReq) string { @@ -34,7 +35,7 @@ func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*R if e.cache == nil { return nil, nil } - if e.filter != nil && !e.filter(req) { + if e.filterGet != nil && !e.filterGet(req) { return nil, nil } @@ -67,7 +68,12 @@ func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res if e.cache == nil { return nil } - if e.filter != nil && !e.filter(req) { + // if there is a filter on get, we don't want to cache it because its irretrievable + if e.filterGet != nil && !e.filterGet(req) { + return nil + } + // response filter + if e.filterPut != nil && !e.filterPut(req, res) { return nil } diff --git a/proxyd/proxyd/tools/mockserver/handler/handler.go b/proxyd/proxyd/tools/mockserver/handler/handler.go index 0f6880c..0f9bfca 100644 --- a/proxyd/proxyd/tools/mockserver/handler/handler.go +++ b/proxyd/proxyd/tools/mockserver/handler/handler.go @@ -77,7 +77,7 @@ func (mh *MockedHandler) Handler(w http.ResponseWriter, req *http.Request) { for _, r := range requests { method := r["method"] block := "" - if method == "eth_getBlockByNumber" { + if method == "eth_getBlockByNumber" || method == "debug_getRawReceipts" { block = (r["params"].([]interface{})[0]).(string) } diff --git a/proxyd/proxyd/tools/mockserver/node1.yml b/proxyd/proxyd/tools/mockserver/node1.yml index c14f328..313c653 100644 --- a/proxyd/proxyd/tools/mockserver/node1.yml +++ b/proxyd/proxyd/tools/mockserver/node1.yml @@ -41,4 +41,12 @@ "hash": "hash34", "number": "0x3" } - } \ No newline at end of file + } +- method: debug_getRawReceipts + block: 0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560ff + response: > + {"jsonrpc":"2.0","id":1,"result":[]} +- method: debug_getRawReceipts + block: 0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560bb + response: > + {"jsonrpc":"2.0","id":1,"result":["0x02f902c10183037ec5b9010000000000000000000000000200000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000400000000000000000008000000000000000000000000000000000000020000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000200000000000000000000000000000000f901b6f8d994297a60578fd0e13076bf06cfeb2bbcd74f2680d2e1a0e5c486bee358a5fff5e4d70dc5fdaaf14806df125ffde843a8c40db608264812b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000282bde1ac0c0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000016573937382e393939393939393939393939395334393600000000000000000000f8d994297a60578fd0e13076bf06cfeb2bbcd74f2680d2e1a01309ab74031e37b46ee8ce9ff667a17a5c69a500a05d167e4c89ad8b0bc40bf9b8a0000000000000000000000000cd28ab95ae80b31255b2258a116cd2c1a371e0f3000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000652d2a700000000000000000000000000000000000000000000000000000000000000016573937382e393939393939393939393939395334393600000000000000000000","0x02f9039f01830645cdb9010000000020000000000000001000000000000000008004000000000000004000000000000000000002000000000000000000000000000000000000000000000000000000a00000000000000000000000200000000000000000000000000000000000000000000000400000000000000000000000000000000000000008000000000000000000004000400800000020000000000000000000000002000000000000000000000000000000200000000000040000000000000000000000001000000100100200000002000000000100000000010000020000000000000000000000000010000000000000000000000000000000000000000000000000000008040000f90294f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a02ac69ee804d9a7a0984249f508dfab7cb2534b465b6ce1580f99a38ba9c5e631a0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160a0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160a0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a947ad11bb9216bc9dc4cbd488d7618cbfd433d1e75f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0e4bac2a8774c733b2c6da6ddf718b53614f36417dfaac90b23d2747d82d2251580f87a947fd7eea37c53abf356cc80e71144d62cd8af27d3f842a0db5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1ba04bd009c947444055f7e29f16c227e544387b9de8b571888d37f50becee99d76ea00000000000000000000000000000000000000000000000000000000000000001","0x02f90327018307aa96b9010000000000000000000000000028000000000000000000000000000000400000000000000000000000000000000000000000000000020000000000000000000000000000100000000000000000000000000000000000000000000000000000004000001000000000000000000000000000000000004000000000000000000000000000000000008000000000000000000000000000000000000000000000008000000000021000000000000000000000002040000000000000000000000000000000000000000000000000000000000000008000000000000000001000000000000000000000000000010000000000000000000000000000000000000000004000f9021cf9013c94af4159a80b6cc41ed517db1c453d1ef5c2e4db72f863a05e3c1311ea442664e8b1611bfabef659120ea7a0a2cfc0667700bebc69cbffe1a000000000000000000000000000000000000000000000000000000000000b574ea0eef6d16b5a91e0c5aa2df75fbb865f2be353c5052294a97ee3522d6b1ed674bfb8c00000000000000000000000006bebc4925716945d46f0ec336d5c2564f419682c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000b47d4ece2bd3d6457b9abcde547e332832a6881b5e8697f86a16fd1e3006843062eda71ce901c9f888b5abb4736b9ca9bb36748e000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000652d2a70f8db946bebc4925716945d46f0ec336d5c2564f419682cf842a0ff64905f73a67fb594e0f940a8075a860db489ad991e032f48c81123eb52d60ba000000000000000000000000000000000000000000000000000000000000b574eb88000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000034a36c4ece2bd3d6457b9abcde547e332832a6770a0000000000000000000000000000000000000000000000000095094d8053e000000000000000000000000000","0x02f901a70183083234b9010000000000000000040000000000000000000000020000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000040000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000800000000400000001000000000000000000000000000000000000000000000f89df89b94326c977e6efc84e512bb9c30f76e30c160ed06fbf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004281ecf07378ee595c564a59048801330f3084eea0000000000000000000000000cd5d6cadfd3b4c77415859a5d6999f2eea1da5d4a0000000000000000000000000000000000000000000000001158e460913d00000","0x02f9040a0183094189b9010000000000000000000000100000000000000000000000000000000000000000000002000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000008008000000000000000000000000000f902fff902fc94be72771b59f99adc9c07a43295fcada66cb865b9f842a035ee444266c4bfac0b9704ee4fc68807fe38d1e96b299a097442f8169f48d57da00000000000000000000000000000000000000000000000000000000000000027b902a000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000652d2a700000000000000000000000000000000000000000000000000000000000000027000000000000000000000000bc365d43d4761fd03738ebefa5ff3d7ccf8256a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000652e78100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000183635326432363837663661356263626235613934643639370000000000000000000000000000000000000000000000000000000000000000000000000000001836353235626563643837396139613163653935363364643800000000000000000000000000000000000000000000000000000000000000000000000000000000","0x02f9039f01830c0891b9010000000020000000000000001000000000000000008004000000000000004000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000004000000200000000000000000000000000000000000000000000000400000000040000800000000000000000000000008000000000000000040004000400800000020200000000000000000000002000000000000000200000000000000200000000000040000800000000000000000000000000100000000000002000000000100000000000000000000000000000200000000000010000000000000000000000000000000000000000000000000000108000000f90294f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a02ac69ee804d9a7a0984249f508dfab7cb2534b465b6ce1580f99a38ba9c5e631a0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212a0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212a0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a947ad11bb9216bc9dc4cbd488d7618cbfd433d1e75f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0c48bab3c23e29b52ad3d5b5a41b22448c12d59d5f986b479fdcc485cae9e794a80f87a947fd7eea37c53abf356cc80e71144d62cd8af27d3f842a0db5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1ba0d6910807a825d1b186bc97aaa5ae83f44bc11533f05f3bd567dd916e5de20de1a00000000000000000000000000000000000000000000000000000000000000001","0x02f9024a01837e0c08b9010000000000800000000000000100000000000000000000000000000000000000000000000000004000000000000200000000000000000000000000000000001000000000000000000000000000000000000000000040000000000040000000000800000000000000000000000000000000000000000000000000000000000000000000000000400020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000f9013ff9013c94c25708eaf0329888c4db0fa5746647ecca92f257f863a09efce6407f8b9dc575789c1e4cc190f025c452249eb8f7913c49958c9a5535dea000000000000000000000000000000000000000000000000000000000652d2a70a0000000000000000000000000a02d09d454861a0ccd2e8518886cdcec37ecdd2cb8c00000000000000000000000000000000000000000000000000000000000000b220000000000000000000000000000000000000000000000000000000000000b220000000000000000000000000000000000000000000000000000000000000b22000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000066372656174650000000000000000000000000000000000000000000000000000","0x02f9039f018380d310b9010000000020000000000000001000000000000000008404000000000000004000000000000000000002000000000000000000000000000000000040000000000000000000000000000000000000000000200000000000000200000000000000000000000000000000400000000000000000000000000000000000000008000000000000000000004000400800000020000000001000000000000002400000000000000000000000000020200010000000040000000000000000000000000000000100000000400002000000000100000000000000000000000000000000000000000010000000000000000000000000000000000040000000000000000008000000f90294f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a02ac69ee804d9a7a0984249f508dfab7cb2534b465b6ce1580f99a38ba9c5e631a0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482eba0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482ebb8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482eba0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482ebb8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a947ad11bb9216bc9dc4cbd488d7618cbfd433d1e75f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0f9cacd1bc3956dffcb1a7d556066528a92396aa2e7da07a02904b3bfd886661180f87a947fd7eea37c53abf356cc80e71144d62cd8af27d3f842a0db5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1ba00c4d6405faf20e9cb06cdcc5159705b5a4d42ee7ea60a15f20784e82a0b31786a00000000000000000000000000000000000000000000000000000000000000001","0x02f9010901838210d3b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f90c3f018388b358b9010000000000000000000000000000000000000040000000000200000000000004100000000002000000000000000000000000001000000000680000010000000009004020200000000008080008000800000000000000002000000000000000000040040000220000800000002080000800000000000000020000002090000000000000008400000000000000000000000000000000280000000200000000000040000000004000020000000000000000000000000000000000000000000000000000021002000008000000000000000000000000000800000000200100000820001005000000000000000000080000000000000000000000000040000000000000f90b34f89b948bd0e58032e5343c888eba4e72332176fffa7371f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000016345785d8a0000f85894c9b7edc65488bdbb428526b03935090aef40ff03e1a0df21c415b78ed2552cc9971249e32a053abce6087a0ae0fbf3f78db5174a3493a0000000000000000000000000000000000000000000000000000098b591d3ac0ef8d9946f3a314c1279148e53f51af154817c3ef2c827b1e1a0b0c632f55f1e1b3b2c3d82f41ee4716bb4c00f0f5d84cdafc141581bb8757a4fb8a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000022000100000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000000000000000000000000000000000000000f8d99436ebea3941907c438ca8ca2b1065deef21ccdaede1a04e41ee13e03cd5e0446487b524fdc48af6acf26c074dacdbdfb6b574b42c8146b8a0000000000000000000000000000000000000000000000000000000000000277a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000011f66d6864fcce84d5000e1e981dd586766339490000000000000000000000000000000000000000000000000000213ae829dcc5f9019a946f3a314c1279148e53f51af154817c3ef2c827b1e1a0e9bded5f24a4168e4f3bf44e00298c993b22376aad8c58c7dda9718a54cbea82b90160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001140000000000000d54278911f66d6864fcce84d5000e1e981dd58676633949277ab92de63eb7d8a652bf80385906812f92d49c5139000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000a07355534400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000277a00000000000000000000000000000000000000000000000000000000000000144ac853547fa1fe3f4690e452b904578f7d46a531000000000000000000000000000000000000000000000000f9013d9411f66d6864fcce84d5000e1e981dd58676633949f884a0aae74fdfb502b568e3ca6f5aa448a255c90a2f24c4a6104d65ae45f097b37388a00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000277ab8a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000d540000000000000000000000000000000000000000000000000000000000000028b92de63eb7d8a652bf80385906812f92d49c513911f66d6864fcce84d5000e1e981dd58676633949000000000000000000000000000000000000000000000000f85894c9b7edc65488bdbb428526b03935090aef40ff03e1a0df21c415b78ed2552cc9971249e32a053abce6087a0ae0fbf3f78db5174a3493a00000000000000000000000000000000000000000000000000000381ece38c000f8d9946f3a314c1279148e53f51af154817c3ef2c827b1e1a0b0c632f55f1e1b3b2c3d82f41ee4716bb4c00f0f5d84cdafc141581bb8757a4fb8a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000022000100000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000000000000000000000000000000000000000f8d99436ebea3941907c438ca8ca2b1065deef21ccdaede1a04e41ee13e03cd5e0446487b524fdc48af6acf26c074dacdbdfb6b574b42c8146b8a0000000000000000000000000000000000000000000000000000000000000279f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000011f66d6864fcce84d5000e1e981dd586766339490000000000000000000000000000000000000000000000000000d027724dc000f9019a946f3a314c1279148e53f51af154817c3ef2c827b1e1a0e9bded5f24a4168e4f3bf44e00298c993b22376aad8c58c7dda9718a54cbea82b9016000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000114000000000000ac19278911f66d6864fcce84d5000e1e981dd58676633949279f4c11ccee50b70daa47c41849e45316d975b26102000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000a07355534400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000277a00000000000000000000000000000000000000000000000000000000000000144ac853547fa1fe3f4690e452b904578f7d46a531000000000000000000000000000000000000000000000000f9013d9411f66d6864fcce84d5000e1e981dd58676633949f884a0aae74fdfb502b568e3ca6f5aa448a255c90a2f24c4a6104d65ae45f097b37388a00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000279fb8a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000ac1900000000000000000000000000000000000000000000000000000000000000284c11ccee50b70daa47c41849e45316d975b2610211f66d6864fcce84d5000e1e981dd58676633949000000000000000000000000000000000000000000000000f8bb94593be683204ff3501e6e4851956a2da310e393b6f842a0c1e18b2a3583b6bc0879a3f3f657c41adfb512076938208ec0b389d6d19874cba00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531b8607355534400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000277a","0x02f901a7018389386bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000010000000000000000000000000000020000000000000000000800000000000000000000000010000000000000004000000000000000000000000000000000000000000000000000000000000000000000100400000000000000000000000000000400000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000001000000000000000000000000000000000000000000000000f89df89b946199f797b524166122f1c6e6a78ff321389bb686f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e43f4840dad185beef6daa8e7328b521e6a1a2a0a000000000000000000000000000000000000000000000d3c21bcecceda1000000","0x02f908fa01838cb5bdb90100000000004000000000040000000000000000000000005000000022000004000000000000000000000000000000000000000000000000000000000200002000000000000000080000000000080000000000000400004000000000004000000000000000000002010000000000000000000000000008000000000000100000000000000000000000000000000000001000000000000800000000000000200000000a0000000000000000000000100000000000000000080000000000000000000000000002000000000000000000000000200240000000800000000000000000000010000000040000000000000000000000000000000800000000000000000000f907eff9025d9400000000000000adc04c56bf30ac9d3c0aaf14dcf863a09d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31a0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a0000000000000000000000000ea2b4e7f02b859305093f9f4778a19d66ca176d5b901e0392f9c488837a9a75f4b5a139bed7757ebec5091b7e0b6f1ce70cbbbc75050e50000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c0490000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d3664b5e72b46eaba722ab6f43c22dbf4018195400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011e1a300000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000002715ccea428f8c7694f7e78b2c89cb454c5f7294000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c68af0bb140000000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845f9025d9400000000000000adc04c56bf30ac9d3c0aaf14dcf863a09d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31a00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a0000000000000000000000000ea2b4e7f02b859305093f9f4778a19d66ca176d5b901e0ea28a862f3530833f82dc3d97407cf3a23fb593335f2b11f9ade8d62576f14fe0000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c04900000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000002715ccea428f8c7694f7e78b2c89cb454c5f7294000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d3664b5e72b46eaba722ab6f43c22dbf4018195400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011e1a3000000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049f8b99400000000000000adc04c56bf30ac9d3c0aaf14dce1a04b9f2d36e1b4c93de62cc077b00b1a91d84b6c31b4a14e012718dcca230689e7b88000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002392f9c488837a9a75f4b5a139bed7757ebec5091b7e0b6f1ce70cbbbc75050e5ea28a862f3530833f82dc3d97407cf3a23fb593335f2b11f9ade8d62576f14fef89b94d3664b5e72b46eaba722ab6f43c22dbf40181954f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a000000000000000000000000000000000000000adc04c56bf30ac9d3c0aaf14dca00000000000000000000000000000000000000000000000000000000017d78400f89b94d3664b5e72b46eaba722ab6f43c22dbf40181954f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a00000000000000000000000000000000000000000000000000000000011e1a300f89b942715ccea428f8c7694f7e78b2c89cb454c5f7294f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a000000000000000000000000000000000000000adc04c56bf30ac9d3c0aaf14dca0000000000000000000000000000000000000000000000035a66e2580ecd70000f89b942715ccea428f8c7694f7e78b2c89cb454c5f7294f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a000000000000000000000000000000000000000000000000002c68af0bb140000","0x02f9036101838ed0a2b9010000000000000000080000000000000000000000000040000000000008000000000000000000010000002000008000000000004000000000000000000000000000000000000000000001000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000010000000000000000048000000000000000000000000000000400000000000000000000000000000010000000000000000008000000000000000000000000000000000000000020002000000000000000000000000008000000000000000000000000000000000800000000000000000000000008000000000000000000000000000000000f90256f89b94eea85fdf0b05d1e0107a61b4b4db1f345854b952f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000030f7fd07ce431d090f8ec59c3b635a4df42a00a1a0000000000000000000000000c1b71d1ad2de3fcbecda73c3273296dd45863c21a00000000000000000000000000000000000000000000000012f9aa3647286b60ff89b94fb7378d0997b0092be6bbf278ca9b8058c24752ff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c1b71d1ad2de3fcbecda73c3273296dd45863c21a000000000000000000000000030f7fd07ce431d090f8ec59c3b635a4df42a00a1a0000000000000000000000000000000000000000000000001314fb37062980000f901199430f7fd07ce431d090f8ec59c3b635a4df42a00a1e1a03b841dc9ab51e3104bda4f61b41e4271192d22cd19da5ee6e292dc8e2744f713b8e00000000000000000000000009563fdb01bfbf3d6c548c2c64e446cb5900aca88000000000000000000000000c1b71d1ad2de3fcbecda73c3273296dd45863c2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001314fb370629800000000000000000000000000000000000000000000000000012f9aa3647286b60fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8","0x02f905c40183904383b9010000000000000000000008000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000010000000000000000000000000100000020002000000020000000800000000000000000000000200000000000000010000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000800000000000006000000000000800000100000000000000000000000000000000000000000000001000000000020000000000000000000800000000800000000000000000000004000000000000000f904b9f901ba94db249fda431b6385ad5e028f3aa31f3f51ebaef2e1a0cdb9fb741d82c65a081bb855b5e42174193549c537fd57a199609593827cff71b90180000000000000000000000000762f1119123806fc0aa4c58f61a9da096910200b0000000000000000000000000000000000000000000000000000000000004443000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000c7a6b4554482e676f65726c690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d0c7a6b4554482e676f65726c693f616c656f316d397673377a68787632783232673963667a7a74616e6c72356d357a63617334726a616d7768796a6e74336636746737656772717266703676720080c6a47e8d030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f9019a94762f1119123806fc0aa4c58f61a9da096910200be1a08636abd6d0e464fe725a13346c7ac779b73561c705506044a2e6b2cdb1295ea5b901600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000532e91ca086964251519359271b99bd08427314f000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000000000c7a6b4554482e676f65726c690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f616c656f316d397673377a68787632783232673963667a7a74616e6c72356d357a63617334726a616d7768796a6e743366367467376567727172667036767200f9015c94532e91ca086964251519359271b99bd08427314ff863a0fed162bc92844d868aeee15dc79af2ef4aac1ef57805f4eec865983f4d35efd9a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000660aba6ca934d1537a95cc4454469a1026ed6252b8e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000003f616c656f316d397673377a68787632783232673963667a7a74616e6c72356d357a63617334726a616d7768796a6e743366367467376567727172667036767200","0x02f901a7018390a504b9010000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000100000004000000000000000000000000200000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000020000000000000000000000000000000000000000000000000020000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000400000000000001000000000000000000000000000000000000f89df89b94fad6367e97217cc51b4cd838cc086831f81d38c2f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a000000000000000000000000028cd70b79775193ea6f75f0808870b0b9eb1ad01a00000000000000000000000001c5d511f65c99ff3ea07dcca7ed91146ed5351e2a00000000000000000000000000000000000000000000000000000000000000000","0x02f909ad018396c005b901000000400000040000000000000000000000000000000000000000000000000000000000000000000000000000000010000040000000000200000000004020000000000000a0080000080000080000000000000000020000000000800000000000000000000000000000000000000000000000000400000000000000188000000000000000000000001000000000000000010010000020000000000000000000000a0001000000001001000040000000400000000000001000000000000000000000000002000000000000000002000000010000800000000000000000000000000490100000000000000000000001000004000000000010000000000000000000f908a2f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a00000000000000000000000000000000000000000000000000000f4312ed20263f89b94633f534ddc7ccced21f70e3e6956e669daa49d41f863a05548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3a000000000000000000000000000000000000000000000000000000000003d0900f8dd94633f534ddc7ccced21f70e3e6956e669daa49d41f884a0023916d46c0d18491146f8b0bc7d927a62a0559c8ca79920bda7dc7db1fc72f3a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3b84000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000003d0900f8fc9464046eaf582638f26ba3ff17ea51705f1367cbc3f863a051dfc81761c6e241cfba4adcd6a3af365b7b8305f76dc043f1b1b8bc22f73fdea000000000000000000000000000000000000000000000000000000000ffffffffa00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5fab88000000000000000000000000000000000000000000000000000000000000120f200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000003782dace9d900000ffffffffffffffffffffffffffffffffffffffffffffffffffd46bd89546914bf89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a00000000000000000000000000000000000000000000000000000f4312e94f963f89b94633f534ddc7ccced21f70e3e6956e669daa49d41f863a05548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3a000000000000000000000000000000000000000000000000000000000003d0900f8dd94633f534ddc7ccced21f70e3e6956e669daa49d41f884a0023916d46c0d18491146f8b0bc7d927a62a0559c8ca79920bda7dc7db1fc72f3a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3b84000000000000000000000000000000000000000000000000000000000658e7c8000000000000000000000000000000000000000000000000000000000003d0900f8fc9464046eaf582638f26ba3ff17ea51705f1367cbc3f863a051dfc81761c6e241cfba4adcd6a3af365b7b8305f76dc043f1b1b8bc22f73fdea000000000000000000000000000000000000000000000000000000000658e7c80a00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5fab8800000000000000000000000000000000000000000000000000000000000011e9a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003782dace9d900000000000000000000000000000000000000000000000000000002e45f9fa00bf77","0x02f9055c01839a4ac9b90100000000000004000000000000000000000000000000000000000000000000000000000004000000000000000000001000004000000000020000000000402000000000000080080000000000080000000000000000020000000000800000000000000000000000000000000000000000000000000400000000000000100000000000000000000000001000040000000000010010000000000000000000000000400a0800000000001001000040000000400000000000001000000000000004000000008002000000000000000000000100004000000000000000000000010000000090100000000000000000000001000004000000000010000000000000000000f90451f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a0000000000000000000000000000000000000000000000000000000003e7f2450f89b94633f534ddc7ccced21f70e3e6956e669daa49d41f863a05548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a0000000000000000000000000f1675ffed677b2e7ee0c87574ebd279049d9ebf9a000000000000000000000000000000000000000000000000000000000003d0900f8dd94633f534ddc7ccced21f70e3e6956e669daa49d41f884a0023916d46c0d18491146f8b0bc7d927a62a0559c8ca79920bda7dc7db1fc72f3a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a0000000000000000000000000f1675ffed677b2e7ee0c87574ebd279049d9ebf9b84000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000003d0900f8fc94f1675ffed677b2e7ee0c87574ebd279049d9ebf9f863a051dfc81761c6e241cfba4adcd6a3af365b7b8305f76dc043f1b1b8bc22f73fdea000000000000000000000000000000000000000000000000000000000ffffffffa000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54b880ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003782dace9d900000fffffffffffffffffffffffffffffffffffffffffffffff3d3b3ce837117c4f3","0x02f9032701839baf92b9010000000000000000000000000000000000000000000000000000000000400000000000000000000000000200000000000000000000020000001200000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000021000000000000000000000002440000000000000000000000000000000001000000000000000000000000000008000000000000000001000000000000000000000000000000000000008000000000000000000000000000000004000f9021cf9013c94af4159a80b6cc41ed517db1c453d1ef5c2e4db72f863a05e3c1311ea442664e8b1611bfabef659120ea7a0a2cfc0667700bebc69cbffe1a000000000000000000000000000000000000000000000000000000000000b574fa0c16ec891aba6a3f7b381a53cdd85e9d84741f080b35012c1f376edcac6e95f10b8c00000000000000000000000006bebc4925716945d46f0ec336d5c2564f419682c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000f5ce611c7321438f8a30aab16852d68da4f5ab5a4de176e8c0279273cbe8a9919b029628c5cc2def297494bb2b1d1a470ae6ce18000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000652d2a70f8db946bebc4925716945d46f0ec336d5c2564f419682cf842a0ff64905f73a67fb594e0f940a8075a860db489ad991e032f48c81123eb52d60ba000000000000000000000000000000000000000000000000000000000000b574fb88000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000034e4bd611c7321438f8a30aab16852d68da4f59a490000000000000000000000000000000000000000000000000429d069189e0000000000000000000000000000","0x02f901ff01839ceb65b9010000000000010002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000020000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894d5c325d183c592c94998000c5e0eed9e6655c020e1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a0c9afd20a1f61bb629588d82d72e13914812259d13161284b98747c955d62317ef89994d5c325d183c592c94998000c5e0eed9e6655c020e1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb8600336df0b24cf3302afadbc4c828662c60c2cdae602e59beaab7011bf3fd5a223000000000000000000000000000000000000000000000000000000000004d0e5020abc24e9cfc67a97e463deef1ab573cb11d9cd618bedee85c2df85fac6faa2","0x02f901ff01839e2744b9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a035ceccc734a10ae9c593304774276b4b6324223b019c38c6662ab923022b2c8af89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb860022db625db05534a27ea14675f8d22d7c603e0981ceff2b02504791b75c4f56100000000000000000000000000000000000000000000000000000000000d7c61019e776def80129482e5f6282056f7318bedf7bc30a4ae7cdf8814d8d0b28a99","0x02f901ff01839f6317b9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a01331bfa0165c171aefba1e6eebd55d52bc2389726c1d0594f4a9e2f7812b42e4f89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb86002e2bf007e02d68956b61be94c5661c8c8d1fb7ae552a0b2e7f3869d20d7aa0700000000000000000000000000000000000000000000000000000000000d7c620467725c6a4525d13c195701bf88f4f3c65329438cf8a437c928067d9a80556e","0x02f9033f0183a10e1fb9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000040000040000000000000000200000000000000000000002000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000020000000000004000000000000000080000000200400000000000800000000000000000000000000000000000000000000000000000000000000f90234f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a074c803dcb95e04990c3e71db65899a9e0282003a967632e13eb6ce45d598fb45f9013c94de29d060d45901fb19ed6c6e959eb22d8626708ef863a04264ac208b5fde633ccdd42e0f12c3d6d443a4f3779bbf886925b94665b63a22a0073314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82a0000000000000000000000000c3511006c04ef1d78af4c8e0e74ec18a6e64ff9eb8c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f36e2be14d65c7a832d1a863ecfa06c6730634700000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000000000000000f89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb8600427eee54195a8dc4d35b17e5cbc8e18b6e8eebdaf2017f6e12dac62232ca9e000000000000000000000000000000000000000000000000000000000000d7c6300b1121da2e28ad38fb1736373bb7fe4787b73c881995bbd391b17aa113a2cc8","0x02f901ff0183a249e6b9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a0d4d6f16309b51be964a0482c4cd816fadeaa8759216dd9bbc20773ba36c75ccaf89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb8600001d0bd72454b02cd7386951338297e0da0f5a8c1076a24c1425928dce0838300000000000000000000000000000000000000000000000000000000000d7c64028c033938806df04fcc792f72f91ca04c3c20963eec03addb44fecf6fa795de","0x02f901090183a29beeb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9041e0183a8526db9010000000200000000400000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000010000020000000000000000000000000000004000000080000000004000000002000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800200010000000000000000000000000000000000000000000000000000000040000000000000000f90313f8b994caf156a3dd652e2b493fe9e53f3d526d3cbbd4a8e1a0e2db1e7820b0cca1226a7e7d5cc2a3df28542b04da0f0aa7949f2a74519ef5a0b880000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d310000000000000000000000000000000000000000000000000000b588ff18e000000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d310000000000000000000000000000000000000000000000000000000000000000f87994caf156a3dd652e2b493fe9e53f3d526d3cbbd4a8e1a0305bf06329ff886b42ab3ed2979092b17d3a7fc67e7de42ee393a24c8e39fee7b840000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d31000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d31f901da9475d8ec64bf68b364b1f45249774e78df2f401399e1a0c6cb9661759518374091eb98266dd634614ae793b31549daff33e83d6dee0165b901a0000000000000000000000000caf156a3dd652e2b493fe9e53f3d526d3cbbd4a8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004a1c82542ebdb854ece6ce5355b5c48eb299ecd8000000000000000000000000000000000000000000000000000aa87bee53800000000000000000000000000000000000000000000000000000000000000003840000000000000000000000000000000000000000000000000000b588ff18e000000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d31000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000001e347468207465737420636f6c6c656374697665206f6e203136204f6374200000","0x02f908450183ab3259b9010000000000004048000010000004005000000000080000000000000000000000080002000000000000008200000004008450000000000000000000100000200200000001000000000040000008200000000000000000000000000000102000000100000000000000000000000480000000000000000000000000000010000000000200000000024000000000000000000010000000000000000001004200400000020000000000000000000000001400000000000000000000040000000200000000000002000000080000000000020000000000000000040000000000000000001010000000000000000000000000000000000000000000000000800000000000f9073af89b944031bc992179a7742bb99ec99da67f852c11927af863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783a0000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4fa00000000000000000000000000000000000000000000000000000000000000000f89b944031bc992179a7742bb99ec99da67f852c11927af863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783a0000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4fa000000000000000000000000000000000000000000000000000000000000003e8f902de94d9005878cb1a830355dbf4d814a835c54022038ef884a04b388aecf9fa6cc92253704e5975a6129a4f735bdbd99567df4ed0094ee4ceb5a00000000000000000000000000ded20eaea674409ccfeca298385f361ad359a43a00000000000000000000000004200000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000170db9024000000000000000000000000000000000000000000000000000000000001e8480000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000652d2a7000000000000000000000000000000000000000000000000000000000000001a4cbd4ece90000000000000000000000004200000000000000000000000000000000000010000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4f0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000170d00000000000000000000000000000000000000000000000000000000000000e4662a633a0000000000000000000000003c3a81e81dc49a522a592e7622a7e711c06bf354000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa78300000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f901fc94fcdc20eaea674409ccfeca298385f361ad358932f842a0cb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52aa00000000000000000000000004200000000000000000000000000000000000010b901a0000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4f0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000170d00000000000000000000000000000000000000000000000000000000001e848000000000000000000000000000000000000000000000000000000000000000e4662a633a0000000000000000000000003c3a81e81dc49a522a592e7622a7e711c06bf354000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa78300000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f9011d94d029d527e1d700c549f4e04662035b8a8624ce4ff884a0718594027abd4eaed59f95162563e0cc6d0e8d5b86b1c7be8b1b0ac3343d0396a00000000000000000000000004031bc992179a7742bb99ec99da67f852c11927aa0000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000a0000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783b880000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa78300000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000","0xf901090183ab8461b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf901090183abd669b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf903640183ae0bfcb9010000000000100000400000000000000000000000000000084000000000000000000000000000000000000000000000020000000000000000000000000000000000000040008800000000100008000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000010000000000002000000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000020080000000000000040000000080040002000000000000000000000000000000000000000000010000000020000000000000200000000000000000000000000000001002000000000000000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006ea00000000000000000000000009f40916d0dfb2f8f5fb63d8f76826d09041f2eaeb8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000d68efdc6e047f712f4bff82fc800f5309ca0c4e8e7ba32b255b212fef748ed80af200000000000000000000000000000000000000000000000000000000000000148a555e4fc287650f5e8ca1778a35dd44e893d6aa000000000000000000000000f89b949f40916d0dfb2f8f5fb63d8f76826d09041f2eaef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000363413eb82ff4ebda8e70f8e0f9615b684d2f9eda00000000000000000000000000000000000000000000000000de0b6b3a7640000f89b949f40916d0dfb2f8f5fb63d8f76826d09041f2eaef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000363413eb82ff4ebda8e70f8e0f9615b684d2f9eda00000000000000000000000000000000000000000000000000de0b6b3a7640000","0xf903640183b08524b9010000000000002000000000000000000000000000000040000000000000000000000000000000000020000000000000000000000000000000000000000000000000000040000800000000000008000000000001000000000000000000000000000000000000020000000000000000000800000000000020000000000010000000000002004000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000020080000100400000040000000080100002000000000000000000000000000000000000000000010000000020000000000000200000000000000000000000000000001002000000000000000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006ea00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38eb8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001a400c8fb05948cf6527abe1b570154f613139797c9cf1ace53bd3c8f4a8f756a703f60000000000000000000000000000000000000000000000000000000000000014dd69db25f6d620a7bad3023c5d32761d353d3de9000000000000000000000000f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38ea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000005805420c76fdc3d1f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006ea000000000000000000000000095ac38e1c9af5a6d868c1b376e7c0ba9b9251ca5a00000000000000000000000000000000000000000000000005805420c76fdc3d1","0xf903640183b2fe40b9010000000000002000000000000000000000000000000040000000000000000000000000000000000000000000010000000000000000000000000000000000000000000040000800000000000008000000000001000000000100000000000000000000000000020000000000000000000800000000000020000000000010000000000002000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000020080000100000000040000000080100002000000000000000000000000000000000000000000010000000020000000000000200000008000000000000000000000001002000000000000000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006ea00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38eb8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001a400d1b36efb5ef48c1953b391dadade996e546aebdeea81b118ac2eefda2a548bac50000000000000000000000000000000000000000000000000000000000000014dd69db25f6d620a7bad3023c5d32761d353d3de9000000000000000000000000f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38ea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000005735cbdc26b5d43af89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000f7db1e439db7a79a8f91cbde7c0069a97b89c813a00000000000000000000000000000000000000000000000005735cbdc26b5d43a","0xf902c40183b4a7bab9010000000040000000080000000000000000000000000000000001000002000000000000000000000000000000000000000000000000000000000000000000002000000040000000000000000000000000000000000000020000000000000000000000000000000000000040000000000000000000000008000000000000000000000000000010000000000000000000000000000000000002000000000000000000000000000000000000000000200000000000000000000000000000040000000080000020000000000000000000000000000000000000000000010000000000000000000000200000000000000000000000000000000000000000000000000000f901b9f8dc94980205d352f198748b626f6f7c38a8a5663ec981f863a074bbc026808dcba59692d6a8bb20596849ca718e10e2432c6cdf48af865bc5d9a0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000a6bf2be6c60175601bf88217c75dd4b14abb5fbbb860312401a801d73bde5cacb7e2eeba637d09c23f9a18dff6504cbdc8f7d7b2e348312401a801d73bde5cacb7e2eeba637d09c23f9a18dff6504cbdc8f7d7b2e3480000000000000000000000000000000000000000000000000000000000000014f8d994a6bf2be6c60175601bf88217c75dd4b14abb5fbbe1a0293e3a2153dc5c8d3667cbd6ede71a71674b2381e5dc4b40c91ad0e813447c0fb8a0000000000000000000000000980205d352f198748b626f6f7c38a8a5663ec981cf507db63af850463ebaaeffe5f783680e6aa364627d07c6488f5ba313ebbcca000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000","0xf903640183b6bf22b9010000000000002000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000008000000000001000000000000000000000000000000000000020000000000000000000800000000000020000000000010000000008002002000000000000000000000000000000000000002000000000000000000000000000000000040000000000000000000020180000100000000040000000000140002000000000000000000000000000000000000000000000000000020000000000000200000000000000000000000000000001002000000000400000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006fa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38eb8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000073bd808065379db49bfa5292e83432fd6ede06ae24792f0d266ac89be03f7925229ce0000000000000000000000000000000000000000000000000000000000000014dd69db25f6d620a7bad3023c5d32761d353d3de9000000000000000000000000f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38ea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000a7da3311d85502df89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006fa0000000000000000000000000c677f78297d40138b68be496ae34d861dc9edbeba00000000000000000000000000000000000000000000000000a7da3311d85502d","0xf902c40183b86890b9010000000040000000080000000000000000000000000000000001000002000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000020000000000000000000000000000000000000040000000000000000000000008000000000000000000000000000010000000000000000000000000000000000002000000000000000000000000000000000040000000200000000000000100000000000000040000000000040020000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000f901b9f8dc94980205d352f198748b626f6f7c38a8a5663ec981f863a074bbc026808dcba59692d6a8bb20596849ca718e10e2432c6cdf48af865bc5d9a0000000000000000000000000000000000000000000000000000000000000006fa0000000000000000000000000a6bf2be6c60175601bf88217c75dd4b14abb5fbbb86031f7494a39416159f52b17758dda7bdafa4215dcf706dfc06cd6f8933b817d2031f7494a39416159f52b17758dda7bdafa4215dcf706dfc06cd6f8933b817d200000000000000000000000000000000000000000000000000000000000000014f8d994a6bf2be6c60175601bf88217c75dd4b14abb5fbbe1a0293e3a2153dc5c8d3667cbd6ede71a71674b2381e5dc4b40c91ad0e813447c0fb8a0000000000000000000000000980205d352f198748b626f6f7c38a8a5663ec981ea46511277ad0d9d47ff09d07eb1e1298bcefa6a6da798de1e0af884002aceb1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000","0x02f902460183e60720b9010000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008002000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000001000000000000080004002000000000000000000000000000000000000000100000000000020000000000000001000000000000000000000000000000000020000000000000000f9013bf89b9488045945952b374abf696602941b51149bad8ab4f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e8c83b0cb059fe98f4e0bb2b7be404565e5aaa75a00000000000000000000000000000000000000000033b2e3c9fd0803ce8000000f89c9488045945952b374abf696602941b51149bad8ab4f884a02f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0da00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e8c83b0cb059fe98f4e0bb2b7be404565e5aaa75a0000000000000000000000000e8c83b0cb059fe98f4e0bb2b7be404565e5aaa7580","0xf901090183e65928b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183e6f1e1b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99416324d80bfc68b1fec6c288f0dac640a044d2678e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000180ab5200000000000000000000000000000000000000000000000000000000652d2a5500000000000000000000000000000000000000000000000000000000000000074144412f55534400000000000000000000000000000000000000000000000000","0x02f901e50183e78ab2b9010000000000000000000000000400000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000800040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994e7a43467520e4d12d1f9e94b99d6f041786aadcee1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024cba3965500000000000000000000000000000000000000000000000000000000652d2a550000000000000000000000000000000000000000000000000000000000000008574554482f555344000000000000000000000000000000000000000000000000","0x02f901e50183e82377b9010000000000000000000000000000000000000000000000000000000000000000000000080000000000001000000020000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994b52a8b962ff3d8a6a0937896ff3da3879eac64e3e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005f6321e00000000000000000000000000000000000000000000000000000000652d2a560000000000000000000000000000000000000000000000000000000000000008555344542f555344000000000000000000000000000000000000000000000000","0xf901090183e8ef8fb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183e9883cb9010000000002000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000f8dbf8d994117a5ab00f93469bea455f0864ef9ad8d9630cc9e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000007e060100000000000000000000000000000000000000000000000000000000652d2a5800000000000000000000000000000000000000000000000000000000000000074752542f55534400000000000000000000000000000000000000000000000000","0x02f901e50183ea2101b9010000000000000000000000000000000000000000000000000000004000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000800000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994bbbf9614de2b788a66d970b552a79fae6419abdce1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005eebd8700000000000000000000000000000000000000000000000000000000652d2a580000000000000000000000000000000000000000000000000000000000000008465241582f555344000000000000000000000000000000000000000000000000","0x02f901e50183eab9d2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200f8dbf8d99439f46d72bb20c7bcb8a2cdf52630fac1496e859ae1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024f8bc612d00000000000000000000000000000000000000000000000000000000652d2a5a0000000000000000000000000000000000000000000000000000000000000008574554482f555344000000000000000000000000000000000000000000000000","0x02f901e50183eb528bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000020000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d9945ae58e9dec27619572a42dad916e413afa89e46de1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000019d1f00000000000000000000000000000000000000000000000000000000652d2a5c000000000000000000000000000000000000000000000000000000000000000842414e4b2f555344000000000000000000000000000000000000000000000000","0x02f9043a0183ed1237b9010000200000000000000000002080000000000000000000000000010000000000000000000000000000000000000000200000000000000000000000020000000000000000000400000000002008000000300000000000000000000000008000000000000000008000100000000000000000000000000000000000020010000000000000000080000000004000000000000000000001000000080000104000000000000000000000000000000000000000000000000000000000000000000000000000204002000000001000000000000000000000000000001000000000000020000000000000000000000000000000000000000000000001400000000000000000f9032ff87a94b4fbf271143f4fbf7b91a5ded31805e42b2208d6f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000000000000000000000000000000000e8d4a51000f89b94b4fbf271143f4fbf7b91a5ded31805e42b2208d6f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000077f0ea26bae49c9f412a1511730c7c1d3382f697a0000000000000000000000000000000000000000000000000000000e8d4a51000f89b94257c2c98163227062e5a33095a20fc7604ee52b5f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000077f0ea26bae49c9f412a1511730c7c1d3382f697a0000000000000000000000000185d901fe591ce516ed9e192b33da3ef14d53b93a0000000000000000000000000000000000000000000000000042a7d29b88844e5f8799477f0ea26bae49c9f412a1511730c7c1d3382f697e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000007b61de650eaa9bccab150000000000000000000000000000000000000000000000001adafd27de2bd68bf8fc9477f0ea26bae49c9f412a1511730c7c1d3382f697f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000185d901fe591ce516ed9e192b33da3ef14d53b93b8800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e8d4a51000000000000000000000000000000000000000000000000000042a7d29b88844e50000000000000000000000000000000000000000000000000000000000000000","0x02f901e50183edaafcb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000800000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994aac02884a376dc5145389ba37f08b0dde08d3f18e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000b37b80500000000000000000000000000000000000000000000000000000000652d2a5e0000000000000000000000000000000000000000000000000000000000000008445944582f555344000000000000000000000000000000000000000000000000","0x02f901e50183ee43c1b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000008000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400040000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994402a30f83bbfc2203e1fc5d8a9bb41e1b0ddc639e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024df2e4ee100000000000000000000000000000000000000000000000000000000652d2a5f00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183eedc86b9010000000000000000000000000000000000000000000000000000000000000000040000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000f8dbf8d9943ae963e586b6c1d16f371ac0a1260cdaed6a76bde1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024dcbf623800000000000000000000000000000000000000000000000000000000652d2a5f00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183ef753fb9010000000000000000000000000000000000000000000000000000000000000040000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000f8dbf8d994c8f4aeb27fce1f361cda3aadcda992c7ed7b0e74e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000005b67ef00000000000000000000000000000000000000000000000000000000652d2a610000000000000000000000000000000000000000000000000000000000000008444f47452f555344000000000000000000000000000000000000000000000000","0x02f901e50183f00e04b9010000000000000000000000000000000000000000000000000000000004000000000000000000000000001000000000000400000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99460cfba755fac7178e9a8e133699ad2f7dcf6ad9ae1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000229ca2bd00000000000000000000000000000000000000000000000000000000652d2a620000000000000000000000000000000000000000000000000000000000000008555255532f555344000000000000000000000000000000000000000000000000","0x02f901098083f0647ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183f0fd33b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d9945e3b4e52af7f15f4e4e12033d71cfc3afbc7d3c0e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000002c0f30100000000000000000000000000000000000000000000000000000000652d2a6400000000000000000000000000000000000000000000000000000000000000074f4d472f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f195ecb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000f8dbf8d994ffd9e1167e2ad8f323464832ad99a03bda99b7b7e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000150ea000000000000000000000000000000000000000000000000000000000652d2a64000000000000000000000000000000000000000000000000000000000000000847414c412f555344000000000000000000000000000000000000000000000000","0x02f901098083f22563b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183f2be28b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d9940e324d90e9180df65e63438b2af37458b7b7b500e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000004f47321cd00000000000000000000000000000000000000000000000000000000652d2a690000000000000000000000000000000000000000000000000000000000000007424e422f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f356f9b9010000000000000000000000000000000000000000000000000000000000000000000000000000200000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000f8dbf8d9944a7d0e32e82aea46773c348896761addc51dfb11e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000105dd23061100000000000000000000000000000000000000000000000000000000652d2a6900000000000000000000000000000000000000000000000000000000000000074254432f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f3e4dab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99416324d80bfc68b1fec6c288f0dac640a044d2678e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000018227472200000000000000000000000000000000000000000000000000000000652d2a690000000000000000000000000000000000000000000000000000000000000008414156452f555344000000000000000000000000000000000000000000000000","0x02f901e50183f472c7b9010000000000000000000000000400000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000800040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994e7a43467520e4d12d1f9e94b99d6f041786aadcee1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000285d0fb605900000000000000000000000000000000000000000000000000000000652d2a6a0000000000000000000000000000000000000000000000000000000000000008574254432f555344000000000000000000000000000000000000000000000000","0x02f901e50183f50b8cb9010000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000000000000000000010000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99407c4eee621098c526403b30bdcb17b3722719dcee1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024dfc6ecd300000000000000000000000000000000000000000000000000000000652d2a6a00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f59979b9010000000000000000000000000000000000000000000000000000000000000000000000080000000000001000000020000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994b52a8b962ff3d8a6a0937896ff3da3879eac64e3e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000002870fd6b6be00000000000000000000000000000000000000000000000000000000652d2a6a0000000000000000000000000000000000000000000000000000000000000008574254432f555344000000000000000000000000000000000000000000000000","0x02f904240183f78797b9010000000000100000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000800600000000000000000000000000000000000000000001082000000000001001000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000580000000000000000000000200000000001000000000000000000000000000000000000000000000000000000000000000000000000020000000000000004000000000000000000000000020000000000000000000000000000000000000000000000000000000000000020000f90319f901dc949054f0d5f352fafe6ebf0ec14654da0362dc96caf842a0f6a97944f31ea060dfde0566e4167c1a1082551e64b60ecb14d599a9d023d451a00000000000000000000000000000000000000000000000000000000000011eb6b901800000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000006af57e73d328e2a8ec95e01178d1e2a2a387d66a00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000140000000000000000000000012332e3d78c6cc4a1a0c4dae81535bc000065fa80300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000000000000000000000000000d51cfff1e9a1af7c0000000000000000000000000000000000000000000000000000000000000000040001020300000000000000000000000000000000000000000000000000000000f89b949054f0d5f352fafe6ebf0ec14654da0362dc96caf863a00109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271a00000000000000000000000000000000000000000000000000000000000011eb6a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000652d2a70f89b949054f0d5f352fafe6ebf0ec14654da0362dc96caf863a00559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5fa00000000000000000000000000000000000000000000000d51cfff1e9a1af7c00a00000000000000000000000000000000000000000000000000000000000011eb6a000000000000000000000000000000000000000000000000000000000652d2a70","0x02f901e50183f8156cb9010000000000000000000000000000000000000000000000000000004000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000800000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994bbbf9614de2b788a66d970b552a79fae6419abdce1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000001c062a100000000000000000000000000000000000000000000000000000000652d2a6c000000000000000000000000000000000000000000000000000000000000000853414e442f555344000000000000000000000000000000000000000000000000","0x02f901e50183f8ae31b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000f8dbf8d994e5d686595da780e6fbe88c31b77c1225974c89fbe1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024df3d91e000000000000000000000000000000000000000000000000000000000652d2a6d00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f93c06b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200f8dbf8d99439f46d72bb20c7bcb8a2cdf52630fac1496e859ae1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005f4f04b00000000000000000000000000000000000000000000000000000000652d2a6e0000000000000000000000000000000000000000000000000000000000000008555344432f555344000000000000000000000000000000000000000000000000","0x02f901090183f9a412b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901c80183faf748b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000008000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000080000000000000004000000000800000000000000000000000000000000000000000000000200000000000000000400000000010000000000000000000000000000000000000000000000004800200000000000000000004000000010000000000000000000000000000000f8bef8bc94e5ff3b57695079f808a24256734483cd3889fa9ef884a0a7aaf2512769da4e444e3de247be2564225c2e7a8f74cfe528e46e17d24868e2a08a767b2e89133b95f135e6803d3b75eb52b9636707f38d986de63283fd028beba0000000000000000000000000000000000000000000000000000000000000803ca000000000000000000000000000000000000000000000000000000000003c1c98a000000000000000000000000000000000000000000000000000000000652d2a70","0x02f901090183fb5cb4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183fbb880b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183fdf200b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183fe5538b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183febd24b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf90246018401009066b9010000000000000002000000000000000000000000000000000000800000000000000000000000000000000000000800000000000000000000000000000000000000000100000000000000000008020000000000040000000000000000000000000000000000000008000000000000000000100000000000000000800010800000000000000000000000000000010000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000002000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000040000000004000000000000044f9013af89b9470e53130c4638aa80d5a8abf12983f66e0b1d05ff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003e62eb1d9f503f1db36bcfcabdaa7488718eee09a0000000000000000000000000e8c84a631d71e1bb7083d3a82a3a74870a286b97a0000000000000000000000000000000000000000000000000016345785d8a0000f89b943e62eb1d9f503f1db36bcfcabdaa7488718eee09f863a0cb339b570a7f0b25afa7333371ff11192092a0aeace12b671f4c212f2815c6fea0000000000000000000000000000000000000000000000000000000000000235aa0000000000000000000000000e8c84a631d71e1bb7083d3a82a3a74870a286b97a0191ae0366d4470dec94ac0ad040496453fecc903ffbad13da54e39498f84b0ff","0x02f9010a01840100f0f6b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010158f2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840101d1feb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010239f6b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840102a1f2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f90365018401048fdfb9010000000002000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020000000000000002000000000000000000800000008000000000000040000000000000000002000000000000000000000000000100000000200000000000000200000000010000800000000000000000000000008000000000000000000000000000000000000000004000000000000000000000000800000000000000000000008000000002000000000000002000000000000000000000000000000000000800000000400000000000000000000002000000000000000000000004000000000000000000000000200f90259f89b948f6d296024766b919473f0ef3b1f7addd3f710dff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003dd070f9ee183bd667700d668b35b8932438118ea0000000000000000000000000f2ce1c36503401e5fcdecb17b53ac20939ac05d6a000000000000000000000000000000000000000000000000000000000000004d6f89b94d87ba7a50b2e7e660f678a895e4b72e7cb4ccd9cf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000f2ce1c36503401e5fcdecb17b53ac20939ac05d6a00000000000000000000000003dd070f9ee183bd667700d668b35b8932438118ea00000000000000000000000000000000000000000000000000000000000550ab9f9011c943dd070f9ee183bd667700d668b35b8932438118ef863a0c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67a0000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564a0000000000000000000000000f2ce1c36503401e5fcdecb17b53ac20939ac05d6b8a0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2a0000000000000000000000000000000000000000000000000000000000550ab90000000000000000000000000000000000000042fb2bb8fcdc72e6c302336a2800000000000000000000000000000000000000000000000000000000609fce5a000000000000000000000000000000000000000000000000000000000001487c","0x02f9010a01840105994fb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840106126bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf9018601840106f418b9010000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000001000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000010f87bf87994b33a774f60c3eeea880c09bd56f18def648f8fbbe1a0b78ebc573f1f889ca9e1e0fb62c843c836f3d3a2e1f43ef62940e9b894f4ea4cb8400000000000000000000000000000000000000000000000000e507b8392b34d0000000000000000000000000000000000000000000000000000000000652d2a70","0x02f9010a018401075be8b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901c901840108af12b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000100000000000000040000000000000000000000000000000000000000000000020000000000000000000000000000000000080000000100000000000000000000000000000000000000200000000000000000000000000000000000000004000020000800000000000000000000000000000000000000000000000000000000000000000000000000002000400000000000000000000000000000000000040000000000000000000001000000000000000000000000000000000000000000040000f8bef8bc943f97a3e25166de26eef93ad91e382215b21fecf7f884a0a7aaf2512769da4e444e3de247be2564225c2e7a8f74cfe528e46e17d24868e2a048f9d6c5064b083c5c5f17c6f18f8ac529863c506fc2d65b5ebb26571dfa1ec0a00000000000000000000000000000000000000000000000000000000000007097a0000000000000000000000000000000000000000000000000000000000034c740a000000000000000000000000000000000000000000000000000000000652d2a70","0x02f9010a01840109284ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840109a406b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010a1d2eb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010a8536b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010aead2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010b52b2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010bc95ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010c428ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf901a80184010cc606b9010000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000080000000000000020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000040001000000000000000000000000000000000000000002000000008000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000f89df89b94ccb2505976e9d2fd355c73c3f1a004446d1dfedaf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c813edb526830d24a2ce5801d9ef5026a3967529a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000a"]} From ca70e3a37fa181faf7fa9db0939a94a97517405b Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 19 Oct 2023 11:32:46 -0700 Subject: [PATCH 157/212] mod --- proxyd/proxyd/go.mod | 7 ++++--- proxyd/proxyd/go.sum | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 04f6eb7..3d06ea8 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -33,7 +33,7 @@ require ( 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-20231017151438-5807b5917615 // 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 @@ -43,7 +43,7 @@ require ( github.com/deckarep/golang-set/v2 v2.3.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/ethereum/c-kzg-4844 v0.3.1 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/getsentry/sentry-go v0.25.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -60,11 +60,12 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 4ea7906..c4185d8 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -66,6 +66,8 @@ github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoG github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= github.com/cockroachdb/pebble v0.0.0-20231017151438-5807b5917615 h1:KU3fuJMEQsorXh5QxKQKuEUkD5bef0SYFh9bqxdo2KA= github.com/cockroachdb/pebble v0.0.0-20231017151438-5807b5917615/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= +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.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= @@ -118,6 +120,8 @@ github.com/ethereum/c-kzg-4844 v0.3.0 h1:3Y3hD6l5i0dEYsBL50C+Om644kve3pNqoAcvE26 github.com/ethereum/c-kzg-4844 v0.3.0/go.mod h1:WI2Nd82DMZAAZI1wV2neKGost9EKjvbpQR9OqE5Qqa8= github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +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/ethereum/go-ethereum v1.12.1 h1:1kXDPxhLfyySuQYIfRxVBGYuaHdxNNxevA73vjIwsgk= github.com/ethereum/go-ethereum v1.12.1/go.mod h1:zKetLweqBR8ZS+1O9iJWI8DvmmD2NzD19apjEWDCsnw= github.com/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= @@ -289,6 +293,8 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -366,6 +372,8 @@ github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8u github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= @@ -419,6 +427,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= From 7986edeb267f05eb825fab55fd43a7a2edae5327 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 19 Oct 2023 11:46:24 -0700 Subject: [PATCH 158/212] betterer tests --- .../proxyd/integration_tests/caching_test.go | 25 +++++++++++++++++++ .../integration_tests/mock_backend_test.go | 1 + .../integration_tests/testdata/caching.toml | 1 + 3 files changed, 27 insertions(+) diff --git a/proxyd/proxyd/integration_tests/caching_test.go b/proxyd/proxyd/integration_tests/caching_test.go index e4a64d9..e74b85b 100644 --- a/proxyd/proxyd/integration_tests/caching_test.go +++ b/proxyd/proxyd/integration_tests/caching_test.go @@ -27,6 +27,7 @@ func TestCaching(t *testing.T) { hdlr.SetRoute("eth_getTransactionByBlockHashAndIndex", "999", "eth_getTransactionByBlockHashAndIndex") hdlr.SetRoute("eth_getUncleByBlockHashAndIndex", "999", "eth_getUncleByBlockHashAndIndex") hdlr.SetRoute("eth_getTransactionReceipt", "999", "eth_getTransactionReceipt") + hdlr.SetRoute("debug_getRawReceipts", "999", "debug_getRawReceipts") /* not cacheable */ hdlr.SetRoute("eth_getBlockByNumber", "999", "eth_getBlockByNumber") hdlr.SetRoute("eth_blockNumber", "999", "eth_blockNumber") @@ -180,6 +181,30 @@ func TestCaching(t *testing.T) { RequireEqualJSON(t, resRaw, resCache) require.Equal(t, 2, countRequests(backend, "eth_getBlockByHash")) }) + + t.Run("debug_getRawReceipts with 0 receipts should not be cached", func(t *testing.T) { + backend.Reset() + hdlr.SetRoute("debug_getRawReceipts", "999", []string{}) + resRaw, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560ff"}) + require.NoError(t, err) + resCache, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560ff"}) + require.NoError(t, err) + RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":[]}"), resRaw) + RequireEqualJSON(t, resRaw, resCache) + require.Equal(t, 2, countRequests(backend, "debug_getRawReceipts")) + }) + + t.Run("debug_getRawReceipts with more than 0 receipts should be cached", func(t *testing.T) { + backend.Reset() + hdlr.SetRoute("debug_getRawReceipts", "999", []string{"a"}) + resRaw, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560bb"}) + require.NoError(t, err) + resCache, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560bb"}) + require.NoError(t, err) + RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":[\"a\"]}"), resRaw) + RequireEqualJSON(t, resRaw, resCache) + require.Equal(t, 1, countRequests(backend, "debug_getRawReceipts")) + }) } func TestBatchCaching(t *testing.T) { diff --git a/proxyd/proxyd/integration_tests/mock_backend_test.go b/proxyd/proxyd/integration_tests/mock_backend_test.go index ade879c..bf45d03 100644 --- a/proxyd/proxyd/integration_tests/mock_backend_test.go +++ b/proxyd/proxyd/integration_tests/mock_backend_test.go @@ -77,6 +77,7 @@ func (h *BatchRPCResponseRouter) SetRoute(method string, id string, result inter switch result.(type) { case string: + case []string: case nil: break default: diff --git a/proxyd/proxyd/integration_tests/testdata/caching.toml b/proxyd/proxyd/integration_tests/testdata/caching.toml index f3c1327..41bc65b 100644 --- a/proxyd/proxyd/integration_tests/testdata/caching.toml +++ b/proxyd/proxyd/integration_tests/testdata/caching.toml @@ -33,3 +33,4 @@ eth_getTransactionByHash = "main" eth_getTransactionByBlockHashAndIndex = "main" eth_getUncleByBlockHashAndIndex = "main" eth_getTransactionReceipt = "main" +debug_getRawReceipts = "main" From c82a9a08dee4e3dc3ba0e6643ea5707ba50946e8 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 19 Oct 2023 12:32:39 -0700 Subject: [PATCH 159/212] fix consensus tests --- .../testdata/consensus_responses.yml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml index e4720b0..642c334 100644 --- a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml +++ b/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml @@ -185,6 +185,27 @@ } } - method: debug_getRawReceipts + block: 0x55 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "debug_getRawReceipts" + } + } +- method: debug_getRawReceipts + block: 0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "debug_getRawReceipts" + } + } +- method: debug_getRawReceipts + block: 0x101 response: > { "jsonrpc": "2.0", From 56df3a68924dd2e656c0b476cc784f3b390efc4f Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 19 Oct 2023 12:48:03 -0700 Subject: [PATCH 160/212] add x-served-by header --- proxyd/proxyd/backend.go | 11 ++++++----- proxyd/proxyd/config.go | 2 ++ proxyd/proxyd/proxyd.go | 1 + proxyd/proxyd/server.go | 34 ++++++++++++++++++++++++++-------- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 24b2897..cd57979 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -665,9 +665,9 @@ type BackendGroup struct { Consensus *ConsensusPoller } -func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { +func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, string, error) { if len(rpcReqs) == 0 { - return nil, nil + return nil, "", nil } backends := bg.Backends @@ -731,7 +731,7 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) || errors.Is(err, ErrMethodNotWhitelisted) { - return nil, err + return nil, "", err } if errors.Is(err, ErrBackendOffline) { log.Warn( @@ -773,11 +773,12 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch } } - return res, nil + servedBy := fmt.Sprintf("%s/%s", bg.Name, back.Name) + return res, servedBy, nil } RecordUnserviceableRequest(ctx, RPCRequestSourceHTTP) - return nil, ErrNoBackends + return nil, "", ErrNoBackends } func (bg *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 14e3ece..561c04e 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -24,6 +24,8 @@ type ServerConfig struct { EnableRequestLog bool `toml:"enable_request_log"` MaxRequestBodyLogLen int `toml:"max_request_body_log_len"` + + EnableXServedByHeader bool `toml:"enable_served_by_header"` } type CacheConfig struct { diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 853a38a..84051ab 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -235,6 +235,7 @@ func Start(config *Config) (*Server, func(), error) { resolvedAuth, secondsToDuration(config.Server.TimeoutSeconds), config.Server.MaxUpstreamBatchSize, + config.Server.EnableXServedByHeader, rpcCache, config.RateLimit, config.SenderRateLimit, diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index c03d6de..4f165f0 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -60,6 +60,7 @@ type Server struct { timeout time.Duration maxUpstreamBatchSize int maxBatchSize int + enableServedByHeader bool upgrader *websocket.Upgrader mainLim FrontendRateLimiter overrideLims map[string]FrontendRateLimiter @@ -85,6 +86,7 @@ func NewServer( authenticatedPaths map[string]string, timeout time.Duration, maxUpstreamBatchSize int, + enableServedByHeader bool, cache RPCCache, rateLimitConfig RateLimitConfig, senderRateLimitConfig SenderRateLimitConfig, @@ -175,6 +177,7 @@ func NewServer( authenticatedPaths: authenticatedPaths, timeout: timeout, maxUpstreamBatchSize: maxUpstreamBatchSize, + enableServedByHeader: enableServedByHeader, cache: cache, enableRequestLog: enableRequestLog, maxRequestBodyLogLen: maxRequestBodyLogLen, @@ -354,7 +357,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { return } - batchRes, batchContainsCached, err := s.handleBatchRPC(ctx, reqs, isLimited, true) + batchRes, batchContainsCached, servedBy, err := s.handleBatchRPC(ctx, reqs, isLimited, true) if err == context.DeadlineExceeded { writeRPCError(ctx, w, nil, ErrGatewayTimeout) return @@ -368,14 +371,16 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { writeRPCError(ctx, w, nil, ErrInternal) return } - + if s.enableServedByHeader { + w.Header().Set("x-served-by", servedBy) + } setCacheHeader(w, batchContainsCached) writeBatchRPCRes(ctx, w, batchRes) return } rawBody := json.RawMessage(body) - backendRes, cached, err := s.handleBatchRPC(ctx, []json.RawMessage{rawBody}, isLimited, false) + backendRes, cached, servedBy, err := s.handleBatchRPC(ctx, []json.RawMessage{rawBody}, isLimited, false) if err != nil { if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { @@ -385,11 +390,14 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { writeRPCError(ctx, w, nil, ErrInternal) return } + if s.enableServedByHeader { + w.Header().Set("x-served-by", servedBy) + } setCacheHeader(w, cached) writeRPCRes(ctx, w, backendRes[0]) } -func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isLimited limiterFunc, isBatch bool) ([]*RPCRes, bool, error) { +func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isLimited limiterFunc, isBatch bool) ([]*RPCRes, bool, string, error) { // A request set is transformed into groups of batches. // Each batch group maps to a forwarded JSON-RPC batch request (subject to maxUpstreamBatchSize constraints) // A groupID is used to decouple Requests that have duplicate ID so they're not part of the same batch that's @@ -475,6 +483,7 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL batches[batchGroup] = append(batches[batchGroup], batchElem{parsedReq, i}) } + servedBy := make(map[string]bool, 0) var cached bool for group, batch := range batches { var cacheMisses []batchElem @@ -499,17 +508,18 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL "batch_index", i, ) batchRPCShortCircuitsTotal.Inc() - return nil, false, context.DeadlineExceeded + return nil, false, "", context.DeadlineExceeded } start := i * s.maxUpstreamBatchSize end := int(math.Min(float64(start+s.maxUpstreamBatchSize), float64(len(cacheMisses)))) elems := cacheMisses[start:end] - res, err := s.BackendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) + res, sb, err := s.BackendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) + servedBy[sb] = true if err != nil { if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { - return nil, false, err + return nil, false, "", err } log.Error( "error forwarding RPC batch", @@ -541,7 +551,15 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL } } - return responses, cached, nil + servedByString := "" + for sb, _ := range servedBy { + if servedByString != "" { + servedByString += ", " + } + servedByString += sb + } + + return responses, cached, servedByString, nil } func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { From fed302dd1962eb397c8c0804af6215a636a5325a Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Thu, 19 Oct 2023 12:58:54 -0700 Subject: [PATCH 161/212] fix cache_test --- proxyd/proxyd/cache_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/cache_test.go b/proxyd/proxyd/cache_test.go index 93d0b8e..da47e7c 100644 --- a/proxyd/proxyd/cache_test.go +++ b/proxyd/proxyd/cache_test.go @@ -110,7 +110,7 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { }, res: &RPCRes{ JSONRPC: "2.0", - Result: `{"debug_getRawReceipts":"!"}`, + Result: []interface{}{"a"}, ID: ID, }, name: "debug_getRawReceipts", From a3e2b123636ad20228597ed89be1c8d90a2fe664 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Thu, 19 Oct 2023 21:02:12 -0500 Subject: [PATCH 162/212] Remove space after comma --- proxyd/proxyd/backend.go | 2 +- proxyd/proxyd/backend_test.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 proxyd/proxyd/backend_test.go diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index cd57979..a452a22 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -1170,6 +1170,6 @@ func RecordBatchRPCForward(ctx context.Context, backendName string, reqs []*RPCR } func stripXFF(xff string) string { - ipList := strings.Split(xff, ", ") + ipList := strings.Split(xff, ",") return strings.TrimSpace(ipList[0]) } diff --git a/proxyd/proxyd/backend_test.go b/proxyd/proxyd/backend_test.go new file mode 100644 index 0000000..7be23bf --- /dev/null +++ b/proxyd/proxyd/backend_test.go @@ -0,0 +1,21 @@ +package proxyd + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStripXFF(t *testing.T) { + tests := []struct { + in, out string + }{ + {"1.2.3, 4.5.6, 7.8.9", "1.2.3"}, + {"1.2.3,4.5.6", "1.2.3"}, + {" 1.2.3 , 4.5.6 ", "1.2.3"}, + } + + for _, test := range tests { + actual := stripXFF(test.in) + assert.Equal(t, test.out, actual) + } +} From a064fe06c12822b7ba86064308178a0697f8a5d4 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 21 Oct 2023 13:22:29 -0700 Subject: [PATCH 163/212] chore(proxyd): bump go to 1.21 and update dependencies --- proxyd/proxyd/Dockerfile | 4 +- proxyd/proxyd/go.mod | 13 +- proxyd/proxyd/go.sum | 464 ++------------------------------------- 3 files changed, 21 insertions(+), 460 deletions(-) diff --git a/proxyd/proxyd/Dockerfile b/proxyd/proxyd/Dockerfile index 077cf2c..bf1d7c3 100644 --- a/proxyd/proxyd/Dockerfile +++ b/proxyd/proxyd/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20.4-alpine3.18 as builder +FROM golang:1.21.3-alpine3.18.4 as builder ARG GITCOMMIT=docker ARG GITDATE=docker @@ -12,7 +12,7 @@ WORKDIR /app RUN make proxyd -FROM alpine:3.18 +FROM alpine:3.18.4 RUN apk add bind-tools jq curl bash git redis diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 3d06ea8..cbab0d5 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -1,6 +1,6 @@ module github.com/ethereum-optimism/optimism/proxyd -go 1.20 +go 1.21 require ( github.com/BurntSushi/toml v1.3.2 @@ -26,19 +26,19 @@ require ( github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect - github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect + github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // 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/pebble v0.0.0-20231020221949-babd592d2360 // 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/crate-crypto/go-kzg-4844 v0.6.0 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.3.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect @@ -49,7 +49,6 @@ require ( github.com/go-stack/stack v1.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/gomodule/redigo v1.8.9 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -59,7 +58,6 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect @@ -73,7 +71,7 @@ require ( github.com/supranational/blst v0.3.11 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect + github.com/yuin/gopher-lua v1.1.0 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect @@ -82,6 +80,5 @@ require ( golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.14.0 // indirect google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index c4185d8..982799b 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -1,224 +1,108 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= -github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= -github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= -github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= 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/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/VictoriaMetrics/fastcache v1.9.0 h1:oMwsS6c8abz98B7ytAewQ7M1ZN/Im/iwKoE1euaFvhs= -github.com/VictoriaMetrics/fastcache v1.9.0/go.mod h1:otoTS3xu+6IzF/qByjqzjp3rTuzM3Qf0ScU1UTj97iU= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= 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.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= -github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= 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/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= -github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= -github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= -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/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.2/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-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= -github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= -github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= 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-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= 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-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= -github.com/cockroachdb/pebble v0.0.0-20231017151438-5807b5917615 h1:KU3fuJMEQsorXh5QxKQKuEUkD5bef0SYFh9bqxdo2KA= -github.com/cockroachdb/pebble v0.0.0-20231017151438-5807b5917615/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= -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.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= -github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/pebble v0.0.0-20231020221949-babd592d2360 h1:x1dzGu9e1FYmkG8mL9emtdWD1EzH/17SijnoLvKvPiM= +github.com/cockroachdb/pebble v0.0.0-20231020221949-babd592d2360/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/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= 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.0 h1:1OnSpOykNkUIBIBJKdhwy2p0JlW5o+Az02ICzZmvvdg= -github.com/consensys/gnark-crypto v0.12.0/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= 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/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= -github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= -github.com/crate-crypto/go-kzg-4844 v0.6.0 h1:dJLygEZBHjaHq2R6Wq39lxu7mPzN308VHbmDS1OFhX8= -github.com/crate-crypto/go-kzg-4844 v0.6.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +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/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A= github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -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.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/c-kzg-4844 v0.3.0 h1:3Y3hD6l5i0dEYsBL50C+Om644kve3pNqoAcvE26o9zI= -github.com/ethereum/c-kzg-4844 v0.3.0/go.mod h1:WI2Nd82DMZAAZI1wV2neKGost9EKjvbpQR9OqE5Qqa8= -github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= -github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= 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/ethereum/go-ethereum v1.12.1 h1:1kXDPxhLfyySuQYIfRxVBGYuaHdxNNxevA73vjIwsgk= -github.com/ethereum/go-ethereum v1.12.1/go.mod h1:zKetLweqBR8ZS+1O9iJWI8DvmmD2NzD19apjEWDCsnw= github.com/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUtn9mJW7SsIPB0Q= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= -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/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 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-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= -github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= -github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= -github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= -github.com/go-redsync/redsync/v4 v4.9.4 h1:vRmYusI+qF95XSpApHAdeu+RjyDvxBXbMthbc/x148c= -github.com/go-redsync/redsync/v4 v4.9.4/go.mod h1:RqBDXUw0q+u9FJTeD2gMzGtHeSVV93DiqGl10B9Hn/4= github.com/go-redsync/redsync/v4 v4.10.0 h1:hTeAak4C73mNBQSTq6KCKDFaiIlfC+z5yTTl8fCJuBs= github.com/go-redsync/redsync/v4 v4.10.0/go.mod h1:ZfayzutkgeBmEmBlUR3j+rF6kN44UUGtEdfzhBFZTPc= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -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/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= -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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -226,490 +110,170 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= -github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= -github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= -github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= -github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= -github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= 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.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= -github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= 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.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= -github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= -github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= -github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 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/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -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.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= -github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= -github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= -github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= -github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= -github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= -github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 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.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= -github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rueian/rueidis v0.0.93 h1:cG905akj2+QyHx0x9y4mN0K8vLi6M94QiyoLulXS3l0= -github.com/rueian/rueidis v0.0.93/go.mod h1:lo6LBci0D986usi5Wxjb4RVNaWENKYbHZSnufGJ9bTE= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -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.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.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= -github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= -github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= 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.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= -github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= +github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0= -go.opentelemetry.io/otel/metric v0.35.0/go.mod h1:qAcbhaTRFU6uG8QM7dDo7XvFsWcugziq/5YI065TokQ= -go.opentelemetry.io/otel/sdk v1.12.0/go.mod h1:WYcvtgquYvgODEvxOry5owO2y9MyciW7JqMz6cpXShE= -go.opentelemetry.io/otel/sdk/metric v0.35.0/go.mod h1:eDyp1GxSiwV98kr7w4pzrszQh/eze9MqBqPd2bCPmyE= -go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.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-20181220203305-927f97764cc3/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-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/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-20200813134508-3edf25e44fcc/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 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-20190227155943-e225da77a7e6/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-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/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-20190222072716-a9d3bda3a223/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-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/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-20191010194322-b09406accb47/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/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-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/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.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.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.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 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.5/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/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-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -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/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -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.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/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-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= From 4ed4ae99bae933fccc2e11a69234a8ef4751f1e7 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sat, 21 Oct 2023 13:40:51 -0600 Subject: [PATCH 164/212] proxyd: Handle oversize requests/backend responses Under certain conditions, users could provide batch requests that cause the upstream Geth nodes to return very large responses. These responses were being handled improperly, and rather than returning an error were being truncated and leading to backends being marked as offline. This issue extended to large client requests as well. This PR also enables `pprof` on proxyd, which was used to debug this problem. --- proxyd/proxyd/backend.go | 31 ++++++++++++- proxyd/proxyd/cmd/proxyd/main.go | 37 ++++++++++++++++ proxyd/proxyd/config.go | 2 +- proxyd/proxyd/go.sum | 28 ++++++++---- .../proxyd/integration_tests/batching_test.go | 17 ++++++++ .../testdata/size_limits.toml | 21 +++++++++ .../integration_tests/validation_test.go | 26 +++++++++++ proxyd/proxyd/reader.go | 32 ++++++++++++++ proxyd/proxyd/reader_test.go | 43 +++++++++++++++++++ proxyd/proxyd/server.go | 8 +++- 10 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 proxyd/proxyd/integration_tests/testdata/size_limits.toml create mode 100644 proxyd/proxyd/reader.go create mode 100644 proxyd/proxyd/reader_test.go diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index a452a22..6c699f0 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -98,6 +98,18 @@ var ( HTTPErrorCode: 400, } + ErrRequestBodyTooLarge = &RPCErr{ + Code: JSONRPCErrorInternal - 21, + Message: "request body too large", + HTTPErrorCode: 413, + } + + ErrBackendResponseTooLarge = &RPCErr{ + Code: JSONRPCErrorInternal - 20, + Message: "backend response too large", + HTTPErrorCode: 500, + } + ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") ErrConsensusGetReceiptsCantBeBatched = errors.New("consensus_getReceipts cannot be batched") @@ -339,6 +351,14 @@ func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([] res, err := b.doForward(ctx, reqs, isBatch) switch err { case nil: // do nothing + case ErrBackendResponseTooLarge: + log.Warn( + "backend response too large", + "name", b.Name, + "req_id", GetReqID(ctx), + "max", b.maxResponseSize, + ) + RecordBatchRPCError(ctx, b.Name, reqs, err) case ErrConsensusGetReceiptsCantBeBatched: log.Warn( "Received unsupported batch request for consensus_getReceipts", @@ -543,7 +563,10 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool } defer httpRes.Body.Close() - resB, err := io.ReadAll(io.LimitReader(httpRes.Body, b.maxResponseSize)) + resB, err := io.ReadAll(LimitReader(httpRes.Body, b.maxResponseSize)) + if errors.Is(err, ErrLimitReaderOverLimit) { + return nil, ErrBackendResponseTooLarge + } if err != nil { b.networkErrorsSlidingWindow.Incr() RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) @@ -726,6 +749,8 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch res := make([]*RPCRes, 0) var err error + servedBy := fmt.Sprintf("%s/%s", bg.Name, back.Name) + if len(rpcReqs) > 0 { res, err = back.Forward(ctx, rpcReqs, isBatch) if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || @@ -733,6 +758,9 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch errors.Is(err, ErrMethodNotWhitelisted) { return nil, "", err } + if errors.Is(err, ErrBackendResponseTooLarge) { + return nil, servedBy, err + } if errors.Is(err, ErrBackendOffline) { log.Warn( "skipping offline backend", @@ -773,7 +801,6 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch } } - servedBy := fmt.Sprintf("%s/%s", bg.Name, back.Name) return res, servedBy, nil } diff --git a/proxyd/proxyd/cmd/proxyd/main.go b/proxyd/proxyd/cmd/proxyd/main.go index a97aacb..10a1518 100644 --- a/proxyd/proxyd/cmd/proxyd/main.go +++ b/proxyd/proxyd/cmd/proxyd/main.go @@ -1,8 +1,12 @@ package main import ( + "net" + "net/http" + "net/http/pprof" "os" "os/signal" + "strconv" "syscall" "github.com/BurntSushi/toml" @@ -52,6 +56,17 @@ func main() { ), ) + if config.Server.EnablePprof { + log.Info("starting pprof", "addr", "0.0.0.0", "port", "6060") + pprofSrv := StartPProf("0.0.0.0", 6060) + log.Info("started pprof server", "addr", pprofSrv.Addr) + defer func() { + if err := pprofSrv.Close(); err != nil { + log.Error("failed to stop pprof server", "err", err) + } + }() + } + _, shutdown, err := proxyd.Start(config) if err != nil { log.Crit("error starting proxyd", "err", err) @@ -63,3 +78,25 @@ func main() { log.Info("caught signal, shutting down", "signal", recvSig) shutdown() } + +func StartPProf(hostname string, port int) *http.Server { + mux := http.NewServeMux() + + // have to do below to support multiple servers, since the + // pprof import only uses DefaultServeMux + mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) + mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) + mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) + mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) + mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) + + addr := net.JoinHostPort(hostname, strconv.Itoa(port)) + srv := &http.Server{ + Handler: mux, + Addr: addr, + } + + go srv.ListenAndServe() + + return srv +} diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 561c04e..fefca9f 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -24,7 +24,7 @@ type ServerConfig struct { EnableRequestLog bool `toml:"enable_request_log"` MaxRequestBodyLogLen int `toml:"max_request_body_log_len"` - + EnablePprof bool `toml:"enable_pprof"` EnableXServedByHeader bool `toml:"enable_served_by_header"` } diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 982799b..a54ffb5 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -6,8 +6,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc 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/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= @@ -19,16 +17,17 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r 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/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/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/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= @@ -52,6 +51,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A= github.com/deckarep/golang-set/v2 v2.3.1/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.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -65,15 +65,20 @@ github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUt 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= 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-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= +github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= +github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-redsync/redsync/v4 v4.10.0 h1:hTeAak4C73mNBQSTq6KCKDFaiIlfC+z5yTTl8fCJuBs= github.com/go-redsync/redsync/v4 v4.10.0/go.mod h1:ZfayzutkgeBmEmBlUR3j+rF6kN44UUGtEdfzhBFZTPc= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= @@ -100,6 +105,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/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.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= @@ -126,11 +132,12 @@ github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3x 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-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= @@ -148,6 +155,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J 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/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= @@ -164,6 +172,7 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= +github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -180,6 +189,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= +github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= @@ -190,8 +200,6 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 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/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= -github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= @@ -215,6 +223,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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= @@ -222,7 +231,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/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= @@ -266,12 +274,14 @@ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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/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.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= diff --git a/proxyd/proxyd/integration_tests/batching_test.go b/proxyd/proxyd/integration_tests/batching_test.go index e40745d..c1f8b38 100644 --- a/proxyd/proxyd/integration_tests/batching_test.go +++ b/proxyd/proxyd/integration_tests/batching_test.go @@ -20,6 +20,9 @@ func TestBatching(t *testing.T) { ethAccountsResponse2 := `{"jsonrpc": "2.0", "result": [], "id": 2}` + backendResTooLargeResponse1 := `{"error":{"code":-32020,"message":"backend response too large"},"id":1,"jsonrpc":"2.0"}` + backendResTooLargeResponse2 := `{"error":{"code":-32020,"message":"backend response too large"},"id":2,"jsonrpc":"2.0"}` + type mockResult struct { method string id string @@ -40,6 +43,7 @@ func TestBatching(t *testing.T) { expectedRes string maxUpstreamBatchSize int numExpectedForwards int + maxResponseSizeBytes int64 }{ { name: "backend returns batches out of order", @@ -128,11 +132,24 @@ func TestBatching(t *testing.T) { maxUpstreamBatchSize: 2, numExpectedForwards: 1, }, + { + name: "large upstream response gets dropped", + mocks: []mockResult{chainIDMock1, chainIDMock2}, + reqs: []*proxyd.RPCReq{ + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("2", "eth_chainId", nil), + }, + expectedRes: asArray(backendResTooLargeResponse1, backendResTooLargeResponse2), + maxUpstreamBatchSize: 2, + numExpectedForwards: 1, + maxResponseSizeBytes: 1, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config.Server.MaxUpstreamBatchSize = tt.maxUpstreamBatchSize + config.BackendOptions.MaxResponseSizeBytes = tt.maxResponseSizeBytes handler := tt.handler if handler == nil { diff --git a/proxyd/proxyd/integration_tests/testdata/size_limits.toml b/proxyd/proxyd/integration_tests/testdata/size_limits.toml new file mode 100644 index 0000000..bd4afab --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/size_limits.toml @@ -0,0 +1,21 @@ +whitelist_error_message = "rpc method is not whitelisted custom message" + +[server] +rpc_port = 8545 +max_request_body_size_bytes = 150 + +[backend] +response_timeout_seconds = 1 +max_response_size_bytes = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" \ No newline at end of file diff --git a/proxyd/proxyd/integration_tests/validation_test.go b/proxyd/proxyd/integration_tests/validation_test.go index fed2c8a..5f7d5ae 100644 --- a/proxyd/proxyd/integration_tests/validation_test.go +++ b/proxyd/proxyd/integration_tests/validation_test.go @@ -1,6 +1,7 @@ package integration_tests import ( + "fmt" "os" "strings" "testing" @@ -227,6 +228,31 @@ func TestBatchRPCValidation(t *testing.T) { } } +func TestSizeLimits(t *testing.T) { + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) + defer goodBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + config := ReadConfig("size_limits") + client := NewProxydClient("http://127.0.0.1:8545") + _, shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + payload := strings.Repeat("barf", 1024*1024) + out, code, err := client.SendRequest([]byte(fmt.Sprintf(`{"jsonrpc": "2.0", "method": "eth_chainId", "params": [%s], "id": 1}`, payload))) + require.NoError(t, err) + require.Equal(t, `{"jsonrpc":"2.0","error":{"code":-32021,"message":"request body too large"},"id":null}`, strings.TrimSpace(string(out))) + require.Equal(t, 413, code) + + // The default response is already over the size limit in size_limits.toml. + out, code, err = client.SendRequest([]byte(`{"jsonrpc": "2.0", "method": "eth_chainId", "params": [], "id": 1}`)) + require.NoError(t, err) + require.Equal(t, `{"jsonrpc":"2.0","error":{"code":-32020,"message":"backend response too large"},"id":1}`, strings.TrimSpace(string(out))) + require.Equal(t, 500, code) +} + func asArray(in ...string) string { return "[" + strings.Join(in, ",") + "]" } diff --git a/proxyd/proxyd/reader.go b/proxyd/proxyd/reader.go new file mode 100644 index 0000000..b16301f --- /dev/null +++ b/proxyd/proxyd/reader.go @@ -0,0 +1,32 @@ +package proxyd + +import ( + "errors" + "io" +) + +var ErrLimitReaderOverLimit = errors.New("over read limit") + +func LimitReader(r io.Reader, n int64) io.Reader { return &LimitedReader{r, n} } + +// A LimitedReader reads from R but limits the amount of +// data returned to just N bytes. Each call to Read +// updates N to reflect the new amount remaining. +// Unlike the standard library version, Read returns +// ErrLimitReaderOverLimit when N <= 0. +type LimitedReader struct { + R io.Reader // underlying reader + N int64 // max bytes remaining +} + +func (l *LimitedReader) Read(p []byte) (int, error) { + if l.N <= 0 { + return 0, ErrLimitReaderOverLimit + } + if int64(len(p)) > l.N { + p = p[0:l.N] + } + n, err := l.R.Read(p) + l.N -= int64(n) + return n, err +} diff --git a/proxyd/proxyd/reader_test.go b/proxyd/proxyd/reader_test.go new file mode 100644 index 0000000..2ee2345 --- /dev/null +++ b/proxyd/proxyd/reader_test.go @@ -0,0 +1,43 @@ +package proxyd + +import ( + "github.com/stretchr/testify/require" + "io" + "strings" + "testing" +) + +func TestLimitReader(t *testing.T) { + data := "hellohellohellohello" + r := LimitReader(strings.NewReader(data), 3) + buf := make([]byte, 3) + + // Buffer reads OK + n, err := r.Read(buf) + require.NoError(t, err) + require.Equal(t, 3, n) + + // Buffer is over limit + n, err = r.Read(buf) + require.Equal(t, ErrLimitReaderOverLimit, err) + require.Equal(t, 0, n) + + // Buffer on initial read is over size + buf = make([]byte, 16) + r = LimitReader(strings.NewReader(data), 3) + n, err = r.Read(buf) + require.NoError(t, err) + require.Equal(t, 3, n) + + // test with read all where the limit is less than the data + r = LimitReader(strings.NewReader(data), 3) + out, err := io.ReadAll(r) + require.Equal(t, ErrLimitReaderOverLimit, err) + require.Equal(t, "hel", string(out)) + + // test with read all where the limit is more than the data + r = LimitReader(strings.NewReader(data), 21) + out, err = io.ReadAll(r) + require.NoError(t, err) + require.Equal(t, data, string(out)) +} diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 4f165f0..2b7a1bd 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -319,7 +319,13 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { "remote_ip", xff, ) - body, err := io.ReadAll(io.LimitReader(r.Body, s.maxBodySize)) + body, err := io.ReadAll(LimitReader(r.Body, s.maxBodySize)) + if errors.Is(err, ErrLimitReaderOverLimit) { + log.Error("request body too large", "req_id", GetReqID(ctx)) + RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrRequestBodyTooLarge) + writeRPCError(ctx, w, nil, ErrRequestBodyTooLarge) + return + } if err != nil { log.Error("error reading request body", "err", err) writeRPCError(ctx, w, nil, ErrInternal) From 7abc329946fb8097d285611c5de02195505faa81 Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Sat, 21 Oct 2023 14:20:10 -0700 Subject: [PATCH 165/212] go get -u all --- proxyd/proxyd/go.sum | 1 + 1 file changed, 1 insertion(+) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index a54ffb5..e759ce5 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -138,6 +138,7 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= From c4c88f43079de58f2d59f3267167eb7e74e9a5ba Mon Sep 17 00:00:00 2001 From: felipe <130432649+felipe-op@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:33:42 -0700 Subject: [PATCH 166/212] fix(proxyd): alpine version should be 3.18 (#7781) --- proxyd/proxyd/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/Dockerfile b/proxyd/proxyd/Dockerfile index bf1d7c3..b066e0e 100644 --- a/proxyd/proxyd/Dockerfile +++ b/proxyd/proxyd/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21.3-alpine3.18.4 as builder +FROM golang:1.21.3-alpine3.18 as builder ARG GITCOMMIT=docker ARG GITDATE=docker @@ -12,7 +12,7 @@ WORKDIR /app RUN make proxyd -FROM alpine:3.18.4 +FROM alpine:3.18 RUN apk add bind-tools jq curl bash git redis From 92058943d85e9b7480e7cf299464e1572b827a40 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 24 Oct 2023 16:20:42 +0200 Subject: [PATCH 167/212] ops: buildx bake and fix ci docker publish/release --- proxyd/proxyd/Dockerfile.ignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 proxyd/proxyd/Dockerfile.ignore diff --git a/proxyd/proxyd/Dockerfile.ignore b/proxyd/proxyd/Dockerfile.ignore new file mode 100644 index 0000000..eac1d0b --- /dev/null +++ b/proxyd/proxyd/Dockerfile.ignore @@ -0,0 +1,3 @@ +# ignore everything but proxyd, proxyd defines all its dependencies in the go.mod +* +!/proxyd From 22b72373892cb8087f8bb1c938fba34f9f42c9dc Mon Sep 17 00:00:00 2001 From: Felipe Andrade Date: Fri, 27 Oct 2023 15:34:07 -0700 Subject: [PATCH 168/212] feat(proxyd): support eip-1898 in tag rewritting --- proxyd/proxyd/rewriter.go | 93 +++++++++++++++++++++++----- proxyd/proxyd/rewriter_test.go | 109 ++++++++++++++++++++++++++++++--- 2 files changed, 179 insertions(+), 23 deletions(-) diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index 98b59ec..605787e 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -66,25 +66,26 @@ func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResul "eth_newFilter": return rewriteRange(rctx, req, res, 0) case "debug_getRawReceipts", "consensus_getReceipts": - return rewriteParam(rctx, req, res, 0, true) + return rewriteParam(rctx, req, res, 0, true, false) case "eth_getBalance", "eth_getCode", "eth_getTransactionCount", "eth_call": - return rewriteParam(rctx, req, res, 1, false) - case "eth_getStorageAt": - return rewriteParam(rctx, req, res, 2, false) + return rewriteParam(rctx, req, res, 1, false, true) + case "eth_getStorageAt", + "eth_getProof": + return rewriteParam(rctx, req, res, 2, false, true) case "eth_getBlockTransactionCountByNumber", "eth_getUncleCountByBlockNumber", "eth_getBlockByNumber", "eth_getTransactionByBlockNumberAndIndex", "eth_getUncleByBlockNumberAndIndex": - return rewriteParam(rctx, req, res, 0, false) + return rewriteParam(rctx, req, res, 0, false, false) } return RewriteNone, nil } -func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool) (RewriteResult, error) { +func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool, blockNrOrHash bool) (RewriteResult, error) { var p []interface{} err := json.Unmarshal(req.Params, &p) if err != nil { @@ -99,13 +100,38 @@ func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, requir return RewriteNone, nil } - s, ok := p[pos].(string) - if !ok { - return RewriteOverrideError, errors.New("expected string") - } - val, rw, err := rewriteTag(rctx, s) - if err != nil { - return RewriteOverrideError, err + // support for https://eips.ethereum.org/EIPS/eip-1898 + var val interface{} + var rw bool + if blockNrOrHash { + bnh, err := remarshalBlockNumberOrHash(p[pos]) + if err != nil { + // fallback to string + s, ok := p[pos].(string) + if ok { + val, rw, err = rewriteTag(rctx, s) + if err != nil { + return RewriteOverrideError, err + } + } else { + return RewriteOverrideError, errors.New("expected BlockNumberOrHash or string") + } + } else { + val, rw, err = rewriteTagBlockNumberOrHash(rctx, bnh) + if err != nil { + return RewriteOverrideError, err + } + } + } else { + s, ok := p[pos].(string) + if !ok { + return RewriteOverrideError, errors.New("expected string") + } + + val, rw, err = rewriteTag(rctx, s) + if err != nil { + return RewriteOverrideError, err + } } if rw { @@ -210,14 +236,23 @@ func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (b return false, nil } -func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { +func remarshalBlockNumberOrHash(current interface{}) (*rpc.BlockNumberOrHash, error) { jv, err := json.Marshal(current) if err != nil { - return "", false, err + return nil, err } var bnh rpc.BlockNumberOrHash err = bnh.UnmarshalJSON(jv) + if err != nil { + return nil, err + } + + return &bnh, nil +} + +func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { + bnh, err := remarshalBlockNumberOrHash(current) if err != nil { return "", false, err } @@ -245,3 +280,31 @@ func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { return current, false, nil } + +func rewriteTagBlockNumberOrHash(rctx RewriteContext, current *rpc.BlockNumberOrHash) (*rpc.BlockNumberOrHash, bool, error) { + // this is a hash, not a block number + if current.BlockNumber == nil { + return current, false, nil + } + + switch *current.BlockNumber { + case rpc.PendingBlockNumber, + rpc.EarliestBlockNumber: + return current, false, nil + case rpc.FinalizedBlockNumber: + bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.finalized)) + return &bn, true, nil + case rpc.SafeBlockNumber: + bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.safe)) + return &bn, true, nil + case rpc.LatestBlockNumber: + bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.latest)) + return &bn, true, nil + default: + if current.BlockNumber.Int64() > int64(rctx.latest) { + return nil, false, ErrRewriteBlockOutOfRange + } + } + + return current, false, nil +} diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go index 94bc5c9..1f0d80b 100644 --- a/proxyd/proxyd/rewriter_test.go +++ b/proxyd/proxyd/rewriter_test.go @@ -5,7 +5,9 @@ import ( "strings" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" ) @@ -282,12 +284,14 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { - var p []string + var p []interface{} err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 2, len(p)) require.Equal(t, "0x123", p[0]) - require.Equal(t, hexutil.Uint64(100).String(), p[1]) + bnh, err := remarshalBlockNumberOrHash(p[1]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) }, }, { @@ -314,12 +318,14 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { - var p []string + var p []interface{} err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 2, len(p)) require.Equal(t, "0x123", p[0]) - require.Equal(t, hexutil.Uint64(100).String(), p[1]) + bnh, err := remarshalBlockNumberOrHash(p[1]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) }, }, { @@ -359,13 +365,15 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { - var p []string + var p []interface{} err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 3, len(p)) require.Equal(t, "0x123", p[0]) require.Equal(t, "5", p[1]) - require.Equal(t, hexutil.Uint64(100).String(), p[2]) + bnh, err := remarshalBlockNumberOrHash(p[2]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) }, }, { @@ -377,13 +385,15 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { - var p []string + var p []interface{} err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 3, len(p)) require.Equal(t, "0x123", p[0]) require.Equal(t, "5", p[1]) - require.Equal(t, hexutil.Uint64(100).String(), p[2]) + bnh, err := remarshalBlockNumberOrHash(p[2]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) }, }, { @@ -517,6 +527,88 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteNone, }, + // eip1898 + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash at genesis (blockNumber)", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "10", + map[string]interface{}{ + "blockNumber": "0x0", + }})}, + res: nil, + }, + expected: RewriteNone, + }, + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash at genesis (hash)", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "10", + map[string]interface{}{ + "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + "requireCanonical": true, + }})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0xae851f927ee40de99aabb7461c00f9622ab91d60", p[0]) + require.Equal(t, "10", p[1]) + bnh, err := remarshalBlockNumberOrHash(p[2]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithHash(common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"), true), *bnh) + require.True(t, bnh.RequireCanonical) + }, + }, + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash at latest (blockNumber)", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "10", + map[string]interface{}{ + "blockNumber": "latest", + }})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0xae851f927ee40de99aabb7461c00f9622ab91d60", p[0]) + require.Equal(t, "10", p[1]) + bnh, err := remarshalBlockNumberOrHash(p[2]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) + }, + }, + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "10", + map[string]interface{}{ + "blockNumber": "0x111", + }})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, } // generalize tests for other methods with same interface and behavior @@ -528,6 +620,7 @@ func TestRewriteRequest(t *testing.T) { tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleCountByBlockNumber") tests = generalize(tests, "eth_getBlockByNumber", "eth_getTransactionByBlockNumberAndIndex") tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleByBlockNumberAndIndex") + tests = generalize(tests, "eth_getStorageSlotAt", "eth_getProof") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 798878e45546aa44b6d000dd3f1c927cf5a2d585 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Thu, 2 Nov 2023 14:38:04 -0500 Subject: [PATCH 169/212] proxyd: configurable IP rate limit header --- proxyd/proxyd/config.go | 7 ++++--- proxyd/proxyd/go.sum | 1 - proxyd/proxyd/server.go | 10 +++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index fefca9f..63f557c 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -22,9 +22,9 @@ type ServerConfig struct { MaxUpstreamBatchSize int `toml:"max_upstream_batch_size"` - EnableRequestLog bool `toml:"enable_request_log"` - MaxRequestBodyLogLen int `toml:"max_request_body_log_len"` - EnablePprof bool `toml:"enable_pprof"` + EnableRequestLog bool `toml:"enable_request_log"` + MaxRequestBodyLogLen int `toml:"max_request_body_log_len"` + EnablePprof bool `toml:"enable_pprof"` EnableXServedByHeader bool `toml:"enable_served_by_header"` } @@ -51,6 +51,7 @@ type RateLimitConfig struct { ExemptUserAgents []string `toml:"exempt_user_agents"` ErrorMessage string `toml:"error_message"` MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` + IPHeaderOverride string `toml:"ip_header_override"` } type RateLimitMethodOverride struct { diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index e759ce5..a54ffb5 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -138,7 +138,6 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 2b7a1bd..5d262da 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -44,6 +44,7 @@ const ( defaultWSWriteTimeout = 10 * time.Second maxRequestBodyLogLen = 2000 defaultMaxUpstreamBatchSize = 10 + defaultRateLimitHeader = "X-Forwarded-For" ) var emptyArrayResponse = json.RawMessage("[]") @@ -73,6 +74,7 @@ type Server struct { wsServer *http.Server cache RPCCache srvMu sync.Mutex + rateLimitHeader string } type limiterFunc func(method string) bool @@ -168,6 +170,11 @@ func NewServer( senderLim = limiterFactory(time.Duration(senderRateLimitConfig.Interval), senderRateLimitConfig.Limit, "senders") } + rateLimitHeader := defaultRateLimitHeader + if rateLimitConfig.IPHeaderOverride != "" { + rateLimitHeader = rateLimitConfig.IPHeaderOverride + } + return &Server{ BackendGroups: backendGroups, wsBackendGroup: wsBackendGroup, @@ -192,6 +199,7 @@ func NewServer( allowedChainIds: senderRateLimitConfig.AllowedChainIds, limExemptOrigins: limExemptOrigins, limExemptUserAgents: limExemptUserAgents, + rateLimitHeader: rateLimitHeader, }, nil } @@ -608,7 +616,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context.Context { vars := mux.Vars(r) authorization := vars["authorization"] - xff := r.Header.Get("X-Forwarded-For") + xff := r.Header.Get(s.rateLimitHeader) if xff == "" { ipPort := strings.Split(r.RemoteAddr, ":") if len(ipPort) == 2 { From a5a4a5108da6c66fac5bd71db7512303a8da5584 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Tue, 31 Oct 2023 20:41:55 -0500 Subject: [PATCH 170/212] proxyd: add weighting feature to node selection --- proxyd/proxyd/backend.go | 55 +++++++++++++++++++++++++++++++++-- proxyd/proxyd/backend_test.go | 49 +++++++++++++++++++++++++++++++ proxyd/proxyd/config.go | 4 +++ proxyd/proxyd/proxyd.go | 6 ++-- 4 files changed, 109 insertions(+), 5 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 6c699f0..fbc0b10 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -159,6 +159,8 @@ type Backend struct { latencySlidingWindow *sw.AvgSlidingWindow networkRequestsSlidingWindow *sw.AvgSlidingWindow networkErrorsSlidingWindow *sw.AvgSlidingWindow + + weight int } type BackendOpt func(b *Backend) @@ -239,6 +241,12 @@ func WithConsensusForcedCandidate(forcedCandidate bool) BackendOpt { } } +func WithWeight(weight int) BackendOpt { + return func(b *Backend) { + b.weight = weight + } +} + func WithMaxDegradedLatencyThreshold(maxDegradedLatencyThreshold time.Duration) BackendOpt { return func(b *Backend) { b.maxDegradedLatencyThreshold = maxDegradedLatencyThreshold @@ -683,9 +691,10 @@ func sortBatchRPCResponse(req []*RPCReq, res []*RPCRes) { } type BackendGroup struct { - Name string - Backends []*Backend - Consensus *ConsensusPoller + Name string + Backends []*Backend + UseWeightedRouting bool + Consensus *ConsensusPoller } func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, string, error) { @@ -741,6 +750,8 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch } } rpcReqs = rewrittenReqs + } else if bg.UseWeightedRouting { + backends = randomizeFirstBackendByWeight(backends) } rpcRequestsTotal.Inc() @@ -808,6 +819,39 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch return nil, "", ErrNoBackends } +func randomizeFirstBackendByWeight(backends []*Backend) []*Backend { + if len(backends) == 0 { + return backends + } + + totalWeight := 0 + for _, backend := range backends { + totalWeight += backend.weight + } + + r := rand.New(rand.NewSource(time.Now().UnixNano())) + random := r.Intn(totalWeight) + currentSum := 0 + + for idx, backend := range backends { + currentSum += backend.weight + if currentSum > random { + return moveIndexToStart(backends, idx) + } + } + + log.Warn("unable to select weighted backend, using ordered input") + return backends +} + +func moveIndexToStart(backends []*Backend, index int) []*Backend { + result := make([]*Backend, 0, len(backends)) + result = append(result, backends[index]) + result = append(result, backends[:index]...) + result = append(result, backends[index+1:]...) + return result +} + func (bg *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { for _, back := range bg.Backends { proxier, err := back.ProxyWS(clientConn, methodWhitelist) @@ -872,6 +916,11 @@ func (bg *BackendGroup) loadBalancedConsensusGroup() []*Backend { backendsDegraded[i], backendsDegraded[j] = backendsDegraded[j], backendsDegraded[i] }) + if bg.UseWeightedRouting { + backendsHealthy = randomizeFirstBackendByWeight(backendsHealthy) + backendsDegraded = randomizeFirstBackendByWeight(backendsDegraded) + } + // healthy are put into a priority position // degraded backends are used as fallback backendsHealthy = append(backendsHealthy, backendsDegraded...) diff --git a/proxyd/proxyd/backend_test.go b/proxyd/proxyd/backend_test.go index 7be23bf..3d027b4 100644 --- a/proxyd/proxyd/backend_test.go +++ b/proxyd/proxyd/backend_test.go @@ -19,3 +19,52 @@ func TestStripXFF(t *testing.T) { assert.Equal(t, test.out, actual) } } + +func TestMoveIndexToStart(t *testing.T) { + backends := []*Backend{ + { + Name: "node1", + }, + { + Name: "node1", + }, + { + Name: "node1", + }, + } + + tests := []struct { + index int + out []*Backend + }{ + { + index: 0, + out: []*Backend{ + backends[0], + backends[1], + backends[2], + }, + }, + { + index: 1, + out: []*Backend{ + backends[1], + backends[0], + backends[2], + }, + }, + { + index: 2, + out: []*Backend{ + backends[2], + backends[0], + backends[1], + }, + }, + } + + for _, test := range tests { + result := moveIndexToStart(backends, test.index) + assert.Equal(t, test.out, result) + } +} diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 63f557c..1c01bec 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -98,6 +98,8 @@ type BackendConfig struct { ConsensusSkipPeerCountCheck bool `toml:"consensus_skip_peer_count"` ConsensusForcedCandidate bool `toml:"consensus_forced_candidate"` ConsensusReceiptsTarget string `toml:"consensus_receipts_target"` + + Weight int `toml:"weight"` } type BackendsConfig map[string]*BackendConfig @@ -105,6 +107,8 @@ type BackendsConfig map[string]*BackendConfig type BackendGroupConfig struct { Backends []string `toml:"backends"` + UseWeightedRouting bool `toml:"weighted_routing"` + ConsensusAware bool `toml:"consensus_aware"` ConsensusAsyncHandler string `toml:"consensus_handler"` diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 84051ab..4474cf1 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -144,6 +144,7 @@ func Start(config *Config) (*Server, func(), error) { opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP"))) opts = append(opts, WithConsensusSkipPeerCountCheck(cfg.ConsensusSkipPeerCountCheck)) opts = append(opts, WithConsensusForcedCandidate(cfg.ConsensusForcedCandidate)) + opts = append(opts, WithWeight(cfg.Weight)) receiptsTarget, err := ReadFromEnvOrConfig(cfg.ConsensusReceiptsTarget) if err != nil { @@ -175,8 +176,9 @@ func Start(config *Config) (*Server, func(), error) { backends = append(backends, backendsByName[bName]) } group := &BackendGroup{ - Name: bgName, - Backends: backends, + Name: bgName, + UseWeightedRouting: bg.UseWeightedRouting, + Backends: backends, } backendGroups[bgName] = group } From bd7bd24e66bcb795b511d7c1d9201fe9c53a31f8 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Wed, 1 Nov 2023 15:01:02 -0500 Subject: [PATCH 171/212] Review feedback --- proxyd/proxyd/backend.go | 14 +++++++------- proxyd/proxyd/config.go | 6 +++--- proxyd/proxyd/proxyd.go | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index fbc0b10..98a5da6 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -691,10 +691,10 @@ func sortBatchRPCResponse(req []*RPCReq, res []*RPCRes) { } type BackendGroup struct { - Name string - Backends []*Backend - UseWeightedRouting bool - Consensus *ConsensusPoller + Name string + Backends []*Backend + WeightedRouting bool + Consensus *ConsensusPoller } func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, string, error) { @@ -750,7 +750,7 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch } } rpcReqs = rewrittenReqs - } else if bg.UseWeightedRouting { + } else if bg.WeightedRouting { backends = randomizeFirstBackendByWeight(backends) } @@ -820,7 +820,7 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch } func randomizeFirstBackendByWeight(backends []*Backend) []*Backend { - if len(backends) == 0 { + if len(backends) <= 1 { return backends } @@ -916,7 +916,7 @@ func (bg *BackendGroup) loadBalancedConsensusGroup() []*Backend { backendsDegraded[i], backendsDegraded[j] = backendsDegraded[j], backendsDegraded[i] }) - if bg.UseWeightedRouting { + if bg.WeightedRouting { backendsHealthy = randomizeFirstBackendByWeight(backendsHealthy) backendsDegraded = randomizeFirstBackendByWeight(backendsDegraded) } diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 1c01bec..edfd4f1 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -95,11 +95,11 @@ type BackendConfig struct { ClientKeyFile string `toml:"client_key_file"` StripTrailingXFF bool `toml:"strip_trailing_xff"` + Weight int `toml:"weight"` + ConsensusSkipPeerCountCheck bool `toml:"consensus_skip_peer_count"` ConsensusForcedCandidate bool `toml:"consensus_forced_candidate"` ConsensusReceiptsTarget string `toml:"consensus_receipts_target"` - - Weight int `toml:"weight"` } type BackendsConfig map[string]*BackendConfig @@ -107,7 +107,7 @@ type BackendsConfig map[string]*BackendConfig type BackendGroupConfig struct { Backends []string `toml:"backends"` - UseWeightedRouting bool `toml:"weighted_routing"` + WeightedRouting bool `toml:"weighted_routing"` ConsensusAware bool `toml:"consensus_aware"` ConsensusAsyncHandler string `toml:"consensus_handler"` diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 4474cf1..8db0de0 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -176,9 +176,9 @@ func Start(config *Config) (*Server, func(), error) { backends = append(backends, backendsByName[bName]) } group := &BackendGroup{ - Name: bgName, - UseWeightedRouting: bg.UseWeightedRouting, - Backends: backends, + Name: bgName, + WeightedRouting: bg.WeightedRouting, + Backends: backends, } backendGroups[bgName] = group } From 2ac9981e00f55e00ebfda7ae42957e733a309840 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 5 Nov 2023 20:07:39 -0600 Subject: [PATCH 172/212] Code review feedback --- proxyd/proxyd/backend.go | 76 ++++++++++++-------- proxyd/proxyd/backend_test.go | 132 +++++++++++++++++++++++++--------- proxyd/proxyd/go.mod | 1 + proxyd/proxyd/go.sum | 2 + proxyd/proxyd/proxyd.go | 9 +-- 5 files changed, 152 insertions(+), 68 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 98a5da6..6381994 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -11,6 +11,7 @@ import ( "math" "math/rand" "net/http" + "slices" "sort" "strconv" "strings" @@ -21,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" + "github.com/mroth/weightedrand/v2" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sync/semaphore" @@ -695,6 +697,26 @@ type BackendGroup struct { Backends []*Backend WeightedRouting bool Consensus *ConsensusPoller + weightedChooser *weightedrand.Chooser[*Backend, int] +} + +func NewBackendGroup(name string, backends []*Backend, weightedRouting bool) (*BackendGroup, error) { + choices := make([]weightedrand.Choice[*Backend, int], len(backends)) + for i, backend := range backends { + choices[i] = weightedrand.Choice[*Backend, int]{Item: backend, Weight: backend.weight} + } + + chooser, err := weightedrand.NewChooser(choices...) + if err != nil && weightedRouting { + return nil, err + } + + return &BackendGroup{ + Name: name, + Backends: backends, + WeightedRouting: weightedRouting, + weightedChooser: chooser, + }, nil } func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, string, error) { @@ -702,7 +724,7 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch return nil, "", nil } - backends := bg.Backends + backends := bg.orderedBackendsForRequest() overriddenResponses := make([]*indexedReqRes, 0) rewrittenReqs := make([]*RPCReq, 0, len(rpcReqs)) @@ -710,7 +732,6 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch if bg.Consensus != nil { // When `consensus_aware` is set to `true`, the backend group acts as a load balancer // serving traffic from any backend that agrees in the consensus group - backends = bg.loadBalancedConsensusGroup() // We also rewrite block tags to enforce compliance with consensus rctx := RewriteContext{ @@ -750,8 +771,6 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch } } rpcReqs = rewrittenReqs - } else if bg.WeightedRouting { - backends = randomizeFirstBackendByWeight(backends) } rpcRequestsTotal.Inc() @@ -819,36 +838,19 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch return nil, "", ErrNoBackends } -func randomizeFirstBackendByWeight(backends []*Backend) []*Backend { - if len(backends) <= 1 { - return backends +func moveBackendToStart(choice *Backend, options []*Backend) []*Backend { + result := make([]*Backend, 0, len(options)) + + if slices.Contains(options, choice) { + result = append(result, choice) } - totalWeight := 0 - for _, backend := range backends { - totalWeight += backend.weight - } - - r := rand.New(rand.NewSource(time.Now().UnixNano())) - random := r.Intn(totalWeight) - currentSum := 0 - - for idx, backend := range backends { - currentSum += backend.weight - if currentSum > random { - return moveIndexToStart(backends, idx) + for _, opt := range options { + if opt != choice { + result = append(result, opt) } } - log.Warn("unable to select weighted backend, using ordered input") - return backends -} - -func moveIndexToStart(backends []*Backend, index int) []*Backend { - result := make([]*Backend, 0, len(backends)) - result = append(result, backends[index]) - result = append(result, backends[:index]...) - result = append(result, backends[index+1:]...) return result } @@ -889,6 +891,17 @@ func (bg *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, return nil, ErrNoBackends } +func (bg *BackendGroup) orderedBackendsForRequest() []*Backend { + backends := bg.Backends + if bg.Consensus != nil { + backends = bg.loadBalancedConsensusGroup() + } else if bg.WeightedRouting { + choice := bg.weightedChooser.Pick() + backends = moveBackendToStart(choice, backends) + } + return backends +} + func (bg *BackendGroup) loadBalancedConsensusGroup() []*Backend { cg := bg.Consensus.GetConsensusGroup() @@ -917,8 +930,9 @@ func (bg *BackendGroup) loadBalancedConsensusGroup() []*Backend { }) if bg.WeightedRouting { - backendsHealthy = randomizeFirstBackendByWeight(backendsHealthy) - backendsDegraded = randomizeFirstBackendByWeight(backendsDegraded) + choice := bg.weightedChooser.Pick() + backendsHealthy = moveBackendToStart(choice, backendsHealthy) + backendsDegraded = moveBackendToStart(choice, backendsDegraded) } // healthy are put into a priority position diff --git a/proxyd/proxyd/backend_test.go b/proxyd/proxyd/backend_test.go index 3d027b4..c5a95e6 100644 --- a/proxyd/proxyd/backend_test.go +++ b/proxyd/proxyd/backend_test.go @@ -2,6 +2,7 @@ package proxyd import ( "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" ) @@ -20,51 +21,116 @@ func TestStripXFF(t *testing.T) { } } -func TestMoveIndexToStart(t *testing.T) { - backends := []*Backend{ - { - Name: "node1", - }, - { - Name: "node1", - }, - { - Name: "node1", - }, +func TestCreateBackendGroup(t *testing.T) { + unweightedOne := &Backend{ + Name: "one", + weight: 0, + } + + unweightedTwo := &Backend{ + Name: "two", + weight: 0, + } + + weightedOne := &Backend{ + Name: "one", + weight: 1, + } + + weightedTwo := &Backend{ + Name: "two", + weight: 1, } tests := []struct { - index int - out []*Backend + name string + backends []*Backend + weightedRouting bool + expectError bool }{ { - index: 0, - out: []*Backend{ - backends[0], - backends[1], - backends[2], - }, + name: "weighting disabled", + backends: []*Backend{unweightedOne, unweightedTwo}, + weightedRouting: false, + expectError: false, }, { - index: 1, - out: []*Backend{ - backends[1], - backends[0], - backends[2], - }, + name: "weighting enabled -- all nodes have weight", + backends: []*Backend{weightedOne, weightedTwo}, + weightedRouting: true, + expectError: false, }, { - index: 2, - out: []*Backend{ - backends[2], - backends[0], - backends[1], - }, + name: "weighting enabled -- some nodes have weight", + backends: []*Backend{weightedOne, unweightedTwo}, + weightedRouting: true, + expectError: false, + }, + { + name: "weighting enabled -- no nodes have weight", + backends: []*Backend{unweightedOne, unweightedTwo}, + weightedRouting: true, + expectError: true, }, } for _, test := range tests { - result := moveIndexToStart(backends, test.index) - assert.Equal(t, test.out, result) + result, err := NewBackendGroup(test.name, test.backends, test.weightedRouting) + + if test.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, test.name, result.Name) + assert.Equal(t, test.backends, test.backends) + assert.Equal(t, test.weightedRouting, test.weightedRouting) + } + } + +} + +func TestMoveIndexToStart(t *testing.T) { + one := &Backend{ + Name: "one", + } + + two := &Backend{ + Name: "two", + } + + three := &Backend{ + Name: "three", + } + + tests := []struct { + choice *Backend + input []*Backend + output []*Backend + }{ + { + choice: one, + input: []*Backend{one, two, three}, + output: []*Backend{one, two, three}, + }, + { + choice: two, + input: []*Backend{one, two, three}, + output: []*Backend{two, one, three}, + }, + { + choice: three, + input: []*Backend{one, two}, + output: []*Backend{one, two}, + }, + { + choice: one, + input: []*Backend{one}, + output: []*Backend{one}, + }, + } + + for _, test := range tests { + result := moveBackendToStart(test.choice, test.input) + assert.Equal(t, test.output, result) } } diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index cbab0d5..99d21a1 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -12,6 +12,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v1.0.2 + github.com/mroth/weightedrand/v2 v2.1.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/redis/go-redis/v9 v9.2.1 diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index a54ffb5..a250ff9 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -143,6 +143,8 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQth 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/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= +github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 8db0de0..b99f417 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -175,11 +175,12 @@ func Start(config *Config) (*Server, func(), error) { } backends = append(backends, backendsByName[bName]) } - group := &BackendGroup{ - Name: bgName, - WeightedRouting: bg.WeightedRouting, - Backends: backends, + + group, err := NewBackendGroup(bgName, backends, bg.WeightedRouting) + if err != nil { + return nil, nil, fmt.Errorf("error creating backend group %s: %w", bgName, err) } + backendGroups[bgName] = group } From 20dd9feaf7c9c9ccbd097864776a9d216dfbaa50 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Wed, 8 Nov 2023 19:59:36 -0600 Subject: [PATCH 173/212] Weighted shuffle list --- proxyd/proxyd/backend.go | 69 ++++++-------------- proxyd/proxyd/backend_test.go | 115 ---------------------------------- proxyd/proxyd/go.mod | 2 +- proxyd/proxyd/go.sum | 4 +- proxyd/proxyd/proxyd.go | 9 ++- 5 files changed, 27 insertions(+), 172 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 6381994..80b9bea 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -11,7 +11,6 @@ import ( "math" "math/rand" "net/http" - "slices" "sort" "strconv" "strings" @@ -22,8 +21,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" - "github.com/mroth/weightedrand/v2" "github.com/prometheus/client_golang/prometheus" + "github.com/xaionaro-go/weightedshuffle" "golang.org/x/sync/semaphore" sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" @@ -697,26 +696,6 @@ type BackendGroup struct { Backends []*Backend WeightedRouting bool Consensus *ConsensusPoller - weightedChooser *weightedrand.Chooser[*Backend, int] -} - -func NewBackendGroup(name string, backends []*Backend, weightedRouting bool) (*BackendGroup, error) { - choices := make([]weightedrand.Choice[*Backend, int], len(backends)) - for i, backend := range backends { - choices[i] = weightedrand.Choice[*Backend, int]{Item: backend, Weight: backend.weight} - } - - chooser, err := weightedrand.NewChooser(choices...) - if err != nil && weightedRouting { - return nil, err - } - - return &BackendGroup{ - Name: name, - Backends: backends, - WeightedRouting: weightedRouting, - weightedChooser: chooser, - }, nil } func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, string, error) { @@ -838,22 +817,6 @@ func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch return nil, "", ErrNoBackends } -func moveBackendToStart(choice *Backend, options []*Backend) []*Backend { - result := make([]*Backend, 0, len(options)) - - if slices.Contains(options, choice) { - result = append(result, choice) - } - - for _, opt := range options { - if opt != choice { - result = append(result, opt) - } - } - - return result -} - func (bg *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { for _, back := range bg.Backends { proxier, err := back.ProxyWS(clientConn, methodWhitelist) @@ -891,15 +854,25 @@ func (bg *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, return nil, ErrNoBackends } -func (bg *BackendGroup) orderedBackendsForRequest() []*Backend { - backends := bg.Backends - if bg.Consensus != nil { - backends = bg.loadBalancedConsensusGroup() - } else if bg.WeightedRouting { - choice := bg.weightedChooser.Pick() - backends = moveBackendToStart(choice, backends) +func weightedShuffle(backends []*Backend) { + weight := func(i int) float64 { + return float64(backends[i].weight) + } + + weightedshuffle.ShuffleInplace(backends, weight, nil) +} + +func (bg *BackendGroup) orderedBackendsForRequest() []*Backend { + if bg.Consensus != nil { + return bg.loadBalancedConsensusGroup() + } else if bg.WeightedRouting { + result := make([]*Backend, len(bg.Backends)) + copy(result, bg.Backends) + weightedShuffle(result) + return result + } else { + return bg.Backends } - return backends } func (bg *BackendGroup) loadBalancedConsensusGroup() []*Backend { @@ -930,9 +903,7 @@ func (bg *BackendGroup) loadBalancedConsensusGroup() []*Backend { }) if bg.WeightedRouting { - choice := bg.weightedChooser.Pick() - backendsHealthy = moveBackendToStart(choice, backendsHealthy) - backendsDegraded = moveBackendToStart(choice, backendsDegraded) + weightedShuffle(backendsHealthy) } // healthy are put into a priority position diff --git a/proxyd/proxyd/backend_test.go b/proxyd/proxyd/backend_test.go index c5a95e6..7be23bf 100644 --- a/proxyd/proxyd/backend_test.go +++ b/proxyd/proxyd/backend_test.go @@ -2,7 +2,6 @@ package proxyd import ( "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "testing" ) @@ -20,117 +19,3 @@ func TestStripXFF(t *testing.T) { assert.Equal(t, test.out, actual) } } - -func TestCreateBackendGroup(t *testing.T) { - unweightedOne := &Backend{ - Name: "one", - weight: 0, - } - - unweightedTwo := &Backend{ - Name: "two", - weight: 0, - } - - weightedOne := &Backend{ - Name: "one", - weight: 1, - } - - weightedTwo := &Backend{ - Name: "two", - weight: 1, - } - - tests := []struct { - name string - backends []*Backend - weightedRouting bool - expectError bool - }{ - { - name: "weighting disabled", - backends: []*Backend{unweightedOne, unweightedTwo}, - weightedRouting: false, - expectError: false, - }, - { - name: "weighting enabled -- all nodes have weight", - backends: []*Backend{weightedOne, weightedTwo}, - weightedRouting: true, - expectError: false, - }, - { - name: "weighting enabled -- some nodes have weight", - backends: []*Backend{weightedOne, unweightedTwo}, - weightedRouting: true, - expectError: false, - }, - { - name: "weighting enabled -- no nodes have weight", - backends: []*Backend{unweightedOne, unweightedTwo}, - weightedRouting: true, - expectError: true, - }, - } - - for _, test := range tests { - result, err := NewBackendGroup(test.name, test.backends, test.weightedRouting) - - if test.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - assert.Equal(t, test.name, result.Name) - assert.Equal(t, test.backends, test.backends) - assert.Equal(t, test.weightedRouting, test.weightedRouting) - } - } - -} - -func TestMoveIndexToStart(t *testing.T) { - one := &Backend{ - Name: "one", - } - - two := &Backend{ - Name: "two", - } - - three := &Backend{ - Name: "three", - } - - tests := []struct { - choice *Backend - input []*Backend - output []*Backend - }{ - { - choice: one, - input: []*Backend{one, two, three}, - output: []*Backend{one, two, three}, - }, - { - choice: two, - input: []*Backend{one, two, three}, - output: []*Backend{two, one, three}, - }, - { - choice: three, - input: []*Backend{one, two}, - output: []*Backend{one, two}, - }, - { - choice: one, - input: []*Backend{one}, - output: []*Backend{one}, - }, - } - - for _, test := range tests { - result := moveBackendToStart(test.choice, test.input) - assert.Equal(t, test.output, result) - } -} diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 99d21a1..5130774 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -12,13 +12,13 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v1.0.2 - github.com/mroth/weightedrand/v2 v2.1.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/redis/go-redis/v9 v9.2.1 github.com/rs/cors v1.10.1 github.com/stretchr/testify v1.8.4 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + github.com/xaionaro-go/weightedshuffle v0.0.0-20211213010739-6a74fbc7d24a golang.org/x/sync v0.4.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index a250ff9..b6621d6 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -143,8 +143,6 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQth 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/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= -github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= @@ -200,6 +198,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA 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/xaionaro-go/weightedshuffle v0.0.0-20211213010739-6a74fbc7d24a h1:WS5nQycV+82Ndezq0UcMcGVG416PZgcJPqI/bLM824A= +github.com/xaionaro-go/weightedshuffle v0.0.0-20211213010739-6a74fbc7d24a/go.mod h1:0KAUfC65le2kMu4fnBxm7Xj3PkQ3MBpJbF5oMmqufBc= 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/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index b99f417..fcd7536 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -176,12 +176,11 @@ func Start(config *Config) (*Server, func(), error) { backends = append(backends, backendsByName[bName]) } - group, err := NewBackendGroup(bgName, backends, bg.WeightedRouting) - if err != nil { - return nil, nil, fmt.Errorf("error creating backend group %s: %w", bgName, err) + backendGroups[bgName] = &BackendGroup{ + Name: bgName, + Backends: backends, + WeightedRouting: bg.WeightedRouting, } - - backendGroups[bgName] = group } var wsBackendGroup *BackendGroup From 7175c46a00e9f0e6e2a8d5176adfd3bab83f62d2 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 11 Nov 2023 03:52:09 -0600 Subject: [PATCH 174/212] feat(proxyd): ability to add additional headers to backend requests (#8134) --- proxyd/proxyd/backend.go | 11 +++++++++++ proxyd/proxyd/config.go | 23 ++++++++++++----------- proxyd/proxyd/proxyd.go | 12 ++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 6c699f0..87ba52e 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -139,6 +139,7 @@ type Backend struct { wsURL string authUsername string authPassword string + headers map[string]string client *LimitedHTTPClient dialer *websocket.Dialer maxRetries int @@ -170,6 +171,12 @@ func WithBasicAuth(username, password string) BackendOpt { } } +func WithHeaders(headers map[string]string) BackendOpt { + return func(b *Backend) { + b.headers = headers + } +} + func WithTimeout(timeout time.Duration) BackendOpt { return func(b *Backend) { b.client.Timeout = timeout @@ -535,6 +542,10 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool httpReq.Header.Set("content-type", "application/json") httpReq.Header.Set("X-Forwarded-For", xForwardedFor) + for name, value := range b.headers { + httpReq.Header.Set(name, value) + } + start := time.Now() httpRes, err := b.client.DoLimited(httpReq) if err != nil { diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 63f557c..df75a7d 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -83,17 +83,18 @@ type BackendOptions struct { } type BackendConfig struct { - Username string `toml:"username"` - Password string `toml:"password"` - RPCURL string `toml:"rpc_url"` - WSURL string `toml:"ws_url"` - WSPort int `toml:"ws_port"` - MaxRPS int `toml:"max_rps"` - MaxWSConns int `toml:"max_ws_conns"` - CAFile string `toml:"ca_file"` - ClientCertFile string `toml:"client_cert_file"` - ClientKeyFile string `toml:"client_key_file"` - StripTrailingXFF bool `toml:"strip_trailing_xff"` + Username string `toml:"username"` + Password string `toml:"password"` + RPCURL string `toml:"rpc_url"` + WSURL string `toml:"ws_url"` + WSPort int `toml:"ws_port"` + MaxRPS int `toml:"max_rps"` + MaxWSConns int `toml:"max_ws_conns"` + CAFile string `toml:"ca_file"` + ClientCertFile string `toml:"client_cert_file"` + ClientKeyFile string `toml:"client_key_file"` + StripTrailingXFF bool `toml:"strip_trailing_xff"` + Headers map[string]string `toml:"headers"` ConsensusSkipPeerCountCheck bool `toml:"consensus_skip_peer_count"` ConsensusForcedCandidate bool `toml:"consensus_forced_candidate"` diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 84051ab..fe6aae1 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -130,6 +130,18 @@ func Start(config *Config) (*Server, func(), error) { } opts = append(opts, WithBasicAuth(cfg.Username, passwordVal)) } + + headers := map[string]string{} + for headerName, headerValue := range cfg.Headers { + headerValue, err := ReadFromEnvOrConfig(headerValue) + if err != nil { + return nil, nil, err + } + + headers[headerName] = headerValue + } + opts = append(opts, WithHeaders(headers)) + tlsConfig, err := configureBackendTLS(cfg) if err != nil { return nil, nil, err From adb397c21b3adcf8cefd2984878d8f093f8574df Mon Sep 17 00:00:00 2001 From: kaliubuntu0206 <139627505+kaliubuntu0206@users.noreply.github.com> Date: Sat, 11 Nov 2023 18:54:25 +0900 Subject: [PATCH 175/212] proxyd: add example block range regulation params (#8039) --- proxyd/proxyd/example.config.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proxyd/proxyd/example.config.toml b/proxyd/proxyd/example.config.toml index cce4896..b54b342 100644 --- a/proxyd/proxyd/example.config.toml +++ b/proxyd/proxyd/example.config.toml @@ -98,6 +98,8 @@ backends = ["infura"] # consensus_max_update_threshold = "20s" # Maximum block lag, default 8 # consensus_max_block_lag = 16 +# Maximum block range (for eth_getLogs method), no default +# consensus_max_block_range = 20000 # Minimum peer count, default 3 # consensus_min_peer_count = 4 From e84a39a28f74bb86b2b22773102dba19975a14ba Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Thu, 7 Dec 2023 14:55:57 -0700 Subject: [PATCH 176/212] proxyd: Delete bad test This test was originally checking for a panic in the websocket code. However, we have since re-architected this code and have coverage for this issue elsewhere. Therefore, this test is now superfluous and can be removed. It causes a lot of spam in the logs, and makes debugging other tests harder. --- proxyd/proxyd/integration_tests/ws_test.go | 76 ---------------------- 1 file changed, 76 deletions(-) diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go index bd41991..1548f2a 100644 --- a/proxyd/proxyd/integration_tests/ws_test.go +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -3,91 +3,15 @@ package integration_tests import ( "os" "strings" - "sync" "sync/atomic" "testing" "time" "github.com/ethereum-optimism/optimism/proxyd" - "github.com/ethereum/go-ethereum/log" "github.com/gorilla/websocket" "github.com/stretchr/testify/require" ) -// TestConcurrentWSPanic tests for a panic in the websocket proxy -// that occurred when messages were sent from the upstream to the -// client right after the client sent an invalid request. -func TestConcurrentWSPanic(t *testing.T) { - var backendToProxyConn *websocket.Conn - var setOnce sync.Once - - readyCh := make(chan struct{}, 1) - quitC := make(chan struct{}) - - // Pull out the backend -> proxyd conn so that we can spam it directly. - // Use a sync.Once to make sure we only do that once, for the first - // connection. - backend := NewMockWSBackend(func(conn *websocket.Conn) { - setOnce.Do(func() { - backendToProxyConn = conn - readyCh <- struct{}{} - }) - }, nil, nil) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - - config := ReadConfig("ws") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - client, err := NewProxydWSClient("ws://127.0.0.1:8546", nil, nil) - require.NoError(t, err) - defer shutdown() - - // suppress tons of log messages - oldHandler := log.Root().GetHandler() - log.Root().SetHandler(log.DiscardHandler()) - defer func() { - log.Root().SetHandler(oldHandler) - }() - - <-readyCh - - var wg sync.WaitGroup - wg.Add(2) - // spam messages - go func() { - for { - select { - case <-quitC: - wg.Done() - return - default: - _ = backendToProxyConn.WriteMessage(websocket.TextMessage, []byte("garbage")) - } - } - }() - - // spam invalid RPCs - go func() { - for { - select { - case <-quitC: - wg.Done() - return - default: - _ = client.WriteMessage(websocket.TextMessage, []byte("{\"id\": 1, \"method\": \"eth_foo\", \"params\": [\"newHeads\"]}")) - } - } - }() - - // 1 second is enough to trigger the panic due to - // concurrent write to websocket connection - time.Sleep(time.Second) - close(quitC) - wg.Wait() -} - type backendHandler struct { msgCB atomic.Value closeCB atomic.Value From a6a84c00784bd05a79172019c5e14a55b2a17a59 Mon Sep 17 00:00:00 2001 From: Chomtana Date: Mon, 11 Dec 2023 23:06:56 +0700 Subject: [PATCH 177/212] fix: unmarshaling -> unmarshalling on other files --- proxyd/proxyd/rpc.go | 2 +- proxyd/proxyd/server.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/rpc.go b/proxyd/proxyd/rpc.go index 0c30d64..902e266 100644 --- a/proxyd/proxyd/rpc.go +++ b/proxyd/proxyd/rpc.go @@ -110,7 +110,7 @@ func ParseRPCRes(r io.Reader) (*RPCRes, error) { res := new(RPCRes) if err := json.Unmarshal(body, res); err != nil { - return nil, wrapErr(err, "error unmarshaling RPC response") + return nil, wrapErr(err, "error unmarshalling RPC response") } return res, nil diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 5d262da..a53abf2 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -677,7 +677,7 @@ func (s *Server) isGlobalLimit(method string) bool { func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { var params []string if err := json.Unmarshal(req.Params, ¶ms); err != nil { - log.Debug("error unmarshaling raw transaction params", "err", err, "req_Id", GetReqID(ctx)) + log.Debug("error unmarshalling raw transaction params", "err", err, "req_Id", GetReqID(ctx)) return ErrParseErr } From 678577a3890f751c25503ed6f1a11a3d21e6e635 Mon Sep 17 00:00:00 2001 From: Zach Howard Date: Tue, 12 Dec 2023 13:04:52 -0500 Subject: [PATCH 178/212] proxyd: adds test case, docs for allowing pre-eip155 txns --- proxyd/proxyd/config.go | 1 + proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml | 2 +- proxyd/proxyd/integration_tests/testdata/testdata.txt | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 0e86b0b..bb7fe22 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -135,6 +135,7 @@ type BatchConfig struct { // SenderRateLimitConfig configures the sender-based rate limiter // for eth_sendRawTransaction requests. +// To enable pre-eip155 transactions, add '0' to allowed_chain_ids. type SenderRateLimitConfig struct { Enabled bool Interval TOMLDuration diff --git a/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml index 024858a..c99959d 100644 --- a/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml @@ -18,7 +18,7 @@ eth_chainId = "main" eth_sendRawTransaction = "main" [sender_rate_limit] +allowed_chain_ids = [0, 420] # adding 0 allows pre-EIP-155 transactions enabled = true interval = "1s" limit = 1 -allowed_chain_ids = [420] diff --git a/proxyd/proxyd/integration_tests/testdata/testdata.txt b/proxyd/proxyd/integration_tests/testdata/testdata.txt index 7e31927..14c9124 100644 --- a/proxyd/proxyd/integration_tests/testdata/testdata.txt +++ b/proxyd/proxyd/integration_tests/testdata/testdata.txt @@ -10,4 +10,5 @@ valid transaction data - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTrans valid transaction data - contract call|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6cd17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1b2774201bac50f627403eac1b732459cf70000000000000000000000000000000000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd61145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} valid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} invalid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1} +no chain id (pre eip-155) - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf865808609184e72a00082271094000000000000000000000000000000000000000001001ba0d937ddb66e7788f917864b8e6974cac376b091154db1c25ff8429a6e61016e74a054ced39349e7658b7efceccfabc461e02418eb510124377949cfae8ccf1831af"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1},{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1},{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":123}] From 55757dfacdeda2a6b30095b2ccaf8231e9d3673f Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Wed, 13 Dec 2023 09:50:18 -0800 Subject: [PATCH 179/212] Aggregate typo fixes --- proxyd/proxyd/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/README.md b/proxyd/proxyd/README.md index e3ec1ca..4a3a84b 100644 --- a/proxyd/proxyd/README.md +++ b/proxyd/proxyd/README.md @@ -9,7 +9,7 @@ This tool implements `proxyd`, an RPC request router and proxy. It does the foll 5. Re-write requests and responses to enforce consensus. 6. Load balance requests across backend services. 7. Cache immutable responses from backends. -8. Provides metrics the measure request latency, error rates, and the like. +8. Provides metrics to measure request latency, error rates, and the like. ## Usage From ca17102998e75863c3be25143b95e1491e4e1278 Mon Sep 17 00:00:00 2001 From: Raffaele <151576068+raffaele-oplabs@users.noreply.github.com> Date: Fri, 15 Dec 2023 23:46:37 +0100 Subject: [PATCH 180/212] 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 --- proxyd/proxyd/go.mod | 2 +- proxyd/proxyd/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 5130774..f5df001 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alicebob/miniredis v2.5.0+incompatible github.com/emirpasic/gods v1.18.1 - github.com/ethereum/go-ethereum v1.13.4 + github.com/ethereum/go-ethereum v1.13.5 github.com/go-redsync/redsync/v4 v4.10.0 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index b6621d6..6062492 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -60,8 +60,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 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/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= -github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUtn9mJW7SsIPB0Q= +github.com/ethereum/go-ethereum v1.13.5 h1:U6TCRciCqZRe4FPXmy1sMGxTfuk8P7u2UoinF3VbaFk= +github.com/ethereum/go-ethereum v1.13.5/go.mod h1:yMTu38GSuyxaYzQMViqNmQ1s3cE84abZexQmTgenWk0= 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= From b1522ace51daf7dd4e18339553994c153edd1641 Mon Sep 17 00:00:00 2001 From: felipe <130432649+felipe-op@users.noreply.github.com> Date: Mon, 18 Dec 2023 10:58:18 -0800 Subject: [PATCH 181/212] feat(proxyd): impl proxyd_healthz method (#8658) --- proxyd/proxyd/backend.go | 5 +++-- proxyd/proxyd/server.go | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index b558ff8..f00e951 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -17,6 +17,7 @@ import ( "sync" "time" + sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" @@ -24,8 +25,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/xaionaro-go/weightedshuffle" "golang.org/x/sync/semaphore" - - sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" ) const ( @@ -285,6 +284,8 @@ type indexedReqRes struct { res *RPCRes } +const proxydHealthzMethod = "proxyd_healthz" + const ConsensusGetReceiptsMethod = "consensus_getReceipts" const ReceiptsTargetDebugGetRawReceipts = "debug_getRawReceipts" diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index a53abf2..4c27785 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -435,6 +435,16 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL continue } + // Simple health check + if len(reqs) == 1 && parsedReq.Method == proxydHealthzMethod { + res := &RPCRes{ + ID: parsedReq.ID, + JSONRPC: JSONRPCVersion, + Result: "OK", + } + return []*RPCRes{res}, false, "", nil + } + if err := ValidateRPCReq(parsedReq); err != nil { RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) responses[i] = NewRPCErrorRes(nil, err) From f635c50eb5f0c22323c03e311b9e9ac172688485 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:59:12 -0600 Subject: [PATCH 182/212] build(deps): bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /proxyd (#8674) 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> --- proxyd/proxyd/go.mod | 6 +++--- proxyd/proxyd/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index f5df001..e37defc 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -74,11 +74,11 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/yuin/gopher-lua v1.1.0 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.14.0 // indirect google.golang.org/protobuf v1.31.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 6062492..e9cac7e 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -209,8 +209,8 @@ github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ 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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -247,13 +247,13 @@ 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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= From 6eaf7ee2b48ad78a030c6e3ac364e5440ba454d1 Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Tue, 6 Feb 2024 22:02:02 +0100 Subject: [PATCH 183/212] 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 --- proxyd/proxyd/cmd/proxyd/main.go | 51 ++++++++++++++------ proxyd/proxyd/go.mod | 15 +++--- proxyd/proxyd/go.sum | 34 +++++++------ proxyd/proxyd/integration_tests/util_test.go | 24 +++++---- 4 files changed, 73 insertions(+), 51 deletions(-) diff --git a/proxyd/proxyd/cmd/proxyd/main.go b/proxyd/proxyd/cmd/proxyd/main.go index 10a1518..36e0767 100644 --- a/proxyd/proxyd/cmd/proxyd/main.go +++ b/proxyd/proxyd/cmd/proxyd/main.go @@ -1,17 +1,22 @@ package main import ( + "fmt" "net" "net/http" "net/http/pprof" "os" "os/signal" "strconv" + "strings" "syscall" "github.com/BurntSushi/toml" - "github.com/ethereum-optimism/optimism/proxyd" + "golang.org/x/exp/slog" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/proxyd" ) var ( @@ -23,12 +28,8 @@ var ( func main() { // Set up logger with a default INFO level in case we fail to parse flags. // Otherwise the final critical log won't show what the parsing error was. - log.Root().SetHandler( - log.LvlFilterHandler( - log.LvlInfo, - log.StreamHandler(os.Stdout, log.JSONFormat()), - ), - ) + slog.SetDefault(slog.New(slog.NewJSONHandler( + os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))) log.Info("starting proxyd", "version", GitVersion, "commit", GitCommit, "date", GitDate) @@ -42,19 +43,15 @@ func main() { } // update log level from config - logLevel, err := log.LvlFromString(config.Server.LogLevel) + logLevel, err := LevelFromString(config.Server.LogLevel) if err != nil { - logLevel = log.LvlInfo + logLevel = log.LevelInfo if config.Server.LogLevel != "" { log.Warn("invalid server.log_level set: " + config.Server.LogLevel) } } - log.Root().SetHandler( - log.LvlFilterHandler( - logLevel, - log.StreamHandler(os.Stdout, log.JSONFormat()), - ), - ) + slog.SetDefault(slog.New(slog.NewJSONHandler( + os.Stdout, &slog.HandlerOptions{Level: logLevel}))) if config.Server.EnablePprof { log.Info("starting pprof", "addr", "0.0.0.0", "port", "6060") @@ -79,6 +76,30 @@ func main() { shutdown() } +// LevelFromString returns the appropriate Level from a string name. +// Useful for parsing command line args and configuration files. +// It also converts strings to lowercase. +// Note: copied from op-service/log to avoid monorepo dependency +func LevelFromString(lvlString string) (slog.Level, error) { + lvlString = strings.ToLower(lvlString) // ignore case + switch lvlString { + case "trace", "trce": + return log.LevelTrace, nil + case "debug", "dbug": + return log.LevelDebug, nil + case "info": + return log.LevelInfo, nil + case "warn": + return log.LevelWarn, nil + case "error", "eror": + return log.LevelError, nil + case "crit": + return log.LevelCrit, nil + default: + return log.LevelDebug, fmt.Errorf("unknown level: %v", lvlString) + } +} + func StartPProf(hostname string, port int) *http.Server { mux := http.NewServeMux() diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index e37defc..9bd499b 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alicebob/miniredis v2.5.0+incompatible github.com/emirpasic/gods v1.18.1 - github.com/ethereum/go-ethereum v1.13.5 + github.com/ethereum/go-ethereum v1.13.8 github.com/go-redsync/redsync/v4 v4.10.0 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 @@ -19,7 +19,8 @@ require ( github.com/stretchr/testify v1.8.4 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/xaionaro-go/weightedshuffle v0.0.0-20211213010739-6a74fbc7d24a - golang.org/x/sync v0.4.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.5.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -39,22 +40,23 @@ require ( 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/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.3.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect github.com/getsentry/sentry-go v0.25.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-stack/stack v1.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/gomodule/redigo v1.8.9 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.3 // indirect + github.com/holiman/uint256 v1.2.4 // indirect github.com/klauspost/compress v1.17.1 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -75,11 +77,10 @@ require ( github.com/yuin/gopher-lua v1.1.0 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/mod v0.13.0 // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/tools v0.15.0 // indirect google.golang.org/protobuf v1.31.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index e9cac7e..c93d232 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -42,6 +42,8 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj 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/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= @@ -60,12 +62,14 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 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/ethereum/go-ethereum v1.13.5 h1:U6TCRciCqZRe4FPXmy1sMGxTfuk8P7u2UoinF3VbaFk= -github.com/ethereum/go-ethereum v1.13.5/go.mod h1:yMTu38GSuyxaYzQMViqNmQ1s3cE84abZexQmTgenWk0= +github.com/ethereum/go-ethereum v1.13.8 h1:1od+thJel3tM52ZUNQwvpYOeRHlbkVFZ5S8fhi0Lgsg= +github.com/ethereum/go-ethereum v1.13.8/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA= 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +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.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -81,8 +85,6 @@ github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F4 github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-redsync/redsync/v4 v4.10.0 h1:hTeAak4C73mNBQSTq6KCKDFaiIlfC+z5yTTl8fCJuBs= github.com/go-redsync/redsync/v4 v4.10.0/go.mod h1:ZfayzutkgeBmEmBlUR3j+rF6kN44UUGtEdfzhBFZTPc= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= 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= @@ -120,8 +122,8 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= -github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -211,12 +213,12 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/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.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -224,14 +226,14 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -258,8 +260,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm 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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= 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= diff --git a/proxyd/proxyd/integration_tests/util_test.go b/proxyd/proxyd/integration_tests/util_test.go index db52d2f..e2b1eb6 100644 --- a/proxyd/proxyd/integration_tests/util_test.go +++ b/proxyd/proxyd/integration_tests/util_test.go @@ -10,13 +10,14 @@ import ( "testing" "time" + "github.com/BurntSushi/toml" + "github.com/gorilla/websocket" + "github.com/stretchr/testify/require" + "golang.org/x/exp/slog" + "github.com/ethereum/go-ethereum/log" - "github.com/gorilla/websocket" - - "github.com/BurntSushi/toml" "github.com/ethereum-optimism/optimism/proxyd" - "github.com/stretchr/testify/require" ) type ProxydHTTPClient struct { @@ -129,8 +130,10 @@ type WSMessage struct { Body []byte } -type ProxydWSClientOnMessage func(msgType int, data []byte) -type ProxydWSClientOnClose func(err error) +type ( + ProxydWSClientOnMessage func(msgType int, data []byte) + ProxydWSClientOnClose func(err error) +) func NewProxydWSClient( url string, @@ -183,11 +186,6 @@ func (h *ProxydWSClient) WriteControlMessage(msgType int, msg []byte) error { } func InitLogger() { - log.Root().SetHandler( - log.LvlFilterHandler(log.LvlDebug, - log.StreamHandler( - os.Stdout, - log.TerminalFormat(false), - )), - ) + slog.SetDefault(slog.New( + log.NewTerminalHandlerWithLevel(os.Stdout, slog.LevelDebug, false))) } From 97767bcfa3c551af00b10692b69bfaf2aef23b6a Mon Sep 17 00:00:00 2001 From: Oak Date: Fri, 9 Feb 2024 18:11:30 +0000 Subject: [PATCH 184/212] fix: typo on cache_test eth_call (#9462) --- proxyd/proxyd/cache_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyd/proxyd/cache_test.go b/proxyd/proxyd/cache_test.go index da47e7c..1a5d543 100644 --- a/proxyd/proxyd/cache_test.go +++ b/proxyd/proxyd/cache_test.go @@ -183,7 +183,7 @@ func TestRPCCacheUnsupportedMethod(t *testing.T) { name: "eth_call", req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_gasPrice", + Method: "eth_call", ID: ID, }, }, From a6c9489376d38cdaecece2e470313f6052824570 Mon Sep 17 00:00:00 2001 From: felipe <130432649+felipe-op@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:17:07 -0800 Subject: [PATCH 185/212] feat(proxyd): improved consensus HA error reporting (#9647) * feat(proxyd): improved consensus HA error reporting * error metric should be a counter --- proxyd/proxyd/consensus_tracker.go | 25 +++++++++++++++++++++++-- proxyd/proxyd/metrics.go | 20 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/proxyd/consensus_tracker.go index 158c31b..77e0fdb 100644 --- a/proxyd/proxyd/consensus_tracker.go +++ b/proxyd/proxyd/consensus_tracker.go @@ -194,10 +194,12 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { val, err := ct.client.Get(ct.ctx, key).Result() if err != nil && err != redis.Nil { log.Error("failed to read the lock", "err", err) + RecordGroupConsensusError(ct.backendGroup, "read_lock", err) if ct.leader { ok, err := ct.redlock.Unlock() if err != nil || !ok { log.Error("failed to release the lock after error", "err", err) + RecordGroupConsensusError(ct.backendGroup, "leader_release_lock", err) return } ct.leader = false @@ -210,9 +212,11 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { ok, err := ct.redlock.Extend() if err != nil || !ok { log.Error("failed to extend lock", "err", err, "mutex", ct.redlock.Name(), "val", ct.redlock.Value()) + RecordGroupConsensusError(ct.backendGroup, "leader_extend_lock", err) ok, err := ct.redlock.Unlock() if err != nil || !ok { log.Error("failed to release the lock after error", "err", err) + RecordGroupConsensusError(ct.backendGroup, "leader_release_lock", err) return } ct.leader = false @@ -224,6 +228,7 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { leaderName, err := ct.client.Get(ct.ctx, ct.key(fmt.Sprintf("leader:%s", val))).Result() if err != nil && err != redis.Nil { log.Error("failed to read the remote leader", "err", err) + RecordGroupConsensusError(ct.backendGroup, "read_leader", err) return } ct.leaderName = leaderName @@ -232,16 +237,19 @@ func (ct *RedisConsensusTracker) stateHeartbeat() { val, err := ct.client.Get(ct.ctx, ct.key(fmt.Sprintf("state:%s", val))).Result() if err != nil && err != redis.Nil { log.Error("failed to read the remote state", "err", err) + RecordGroupConsensusError(ct.backendGroup, "read_state", err) return } if val == "" { log.Error("remote state is missing (recent leader election maybe?)") + RecordGroupConsensusError(ct.backendGroup, "read_state_missing", err) return } state := &ConsensusTrackerState{} err = json.Unmarshal([]byte(val), state) if err != nil { log.Error("failed to unmarshal the remote state", "err", err) + RecordGroupConsensusError(ct.backendGroup, "read_unmarshal_state", err) return } @@ -316,13 +324,26 @@ func (ct *RedisConsensusTracker) postPayload(mutexVal string) { jsonState, err := json.Marshal(ct.local.state) if err != nil { log.Error("failed to marshal local", "err", err) + RecordGroupConsensusError(ct.backendGroup, "leader_marshal_local_state", err) + ct.leader = false + return + } + err = ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("state:%s", mutexVal)), jsonState, ct.lockPeriod).Err() + if err != nil { + log.Error("failed to post the state", "err", err) + RecordGroupConsensusError(ct.backendGroup, "leader_post_state", err) ct.leader = false return } - ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("state:%s", mutexVal)), jsonState, ct.lockPeriod) leader, _ := os.LookupEnv("HOSTNAME") - ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("leader:%s", mutexVal)), leader, ct.lockPeriod) + err = ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("leader:%s", mutexVal)), leader, ct.lockPeriod).Err() + if err != nil { + log.Error("failed to post the leader", "err", err) + RecordGroupConsensusError(ct.backendGroup, "leader_post_leader", err) + ct.leader = false + return + } log.Debug("posted state", "state", string(jsonState), "leader", leader) diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 68ca4e8..90a79ab 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -2,6 +2,8 @@ package proxyd import ( "context" + "fmt" + "regexp" "strconv" "strings" "time" @@ -262,6 +264,14 @@ var ( "backend_group_name", }) + consensusHAError = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_ha_error", + Help: "Consensus HA error count", + }, []string{ + "error", + }) + consensusHALatestBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricsNamespace, Name: "group_consensus_ha_latest_block", @@ -465,6 +475,16 @@ func RecordBatchSize(size int) { batchSizeHistogram.Observe(float64(size)) } +var nonAlphanumericRegex = regexp.MustCompile(`[^a-zA-Z ]+`) + +func RecordGroupConsensusError(group *BackendGroup, label string, err error) { + errClean := nonAlphanumericRegex.ReplaceAllString(err.Error(), "") + errClean = strings.ReplaceAll(errClean, " ", "_") + errClean = strings.ReplaceAll(errClean, "__", "_") + label = fmt.Sprintf("%s.%s", label, errClean) + consensusHAError.WithLabelValues(label).Inc() +} + func RecordGroupConsensusHALatestBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { consensusHALatestBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) } From f44f147313450056ad9a247b04e512814139b314 Mon Sep 17 00:00:00 2001 From: felipe <130432649+felipe-op@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:58:00 -0800 Subject: [PATCH 186/212] feat(proxyd): change default cache ttl to 2 hours and make it configurable (#9645) --- proxyd/proxyd/cache.go | 9 ++++----- proxyd/proxyd/config.go | 3 ++- proxyd/proxyd/proxyd.go | 6 +++++- proxyd/proxyd/server.go | 1 + 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/proxyd/proxyd/cache.go b/proxyd/proxyd/cache.go index a93a393..5add4f2 100644 --- a/proxyd/proxyd/cache.go +++ b/proxyd/proxyd/cache.go @@ -21,8 +21,6 @@ type Cache interface { const ( // assuming an average RPCRes size of 3 KB memoryCacheLimit = 4096 - // Set a large ttl to avoid expirations. However, a ttl must be set for volatile-lru to take effect. - redisTTL = 30 * 7 * 24 * time.Hour ) type cache struct { @@ -49,10 +47,11 @@ func (c *cache) Put(ctx context.Context, key string, value string) error { type redisCache struct { rdb *redis.Client prefix string + ttl time.Duration } -func newRedisCache(rdb *redis.Client, prefix string) *redisCache { - return &redisCache{rdb, prefix} +func newRedisCache(rdb *redis.Client, prefix string, ttl time.Duration) *redisCache { + return &redisCache{rdb, prefix, ttl} } func (c *redisCache) namespaced(key string) string { @@ -78,7 +77,7 @@ func (c *redisCache) Get(ctx context.Context, key string) (string, error) { func (c *redisCache) Put(ctx context.Context, key string, value string) error { start := time.Now() - err := c.rdb.SetEx(ctx, c.namespaced(key), value, redisTTL).Err() + err := c.rdb.SetEx(ctx, c.namespaced(key), value, c.ttl).Err() redisCacheDurationSumm.WithLabelValues("SETEX").Observe(float64(time.Since(start).Milliseconds())) if err != nil { diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index bb7fe22..cd0cca1 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -29,7 +29,8 @@ type ServerConfig struct { } type CacheConfig struct { - Enabled bool `toml:"enabled"` + Enabled bool `toml:"enabled"` + TTL TOMLDuration `toml:"ttl"` } type RedisConfig struct { diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 87d9055..7af47b1 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -235,7 +235,11 @@ func Start(config *Config) (*Server, func(), error) { log.Warn("redis is not configured, using in-memory cache") cache = newMemoryCache() } else { - cache = newRedisCache(redisClient, config.Redis.Namespace) + ttl := defaultCacheTtl + if config.Cache.TTL != 0 { + ttl = time.Duration(config.Cache.TTL) + } + cache = newRedisCache(redisClient, config.Redis.Namespace, ttl) } rpcCache = newRPCCache(newCacheWithCompression(cache)) } diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 4c27785..3fa3e00 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -42,6 +42,7 @@ const ( defaultWSHandshakeTimeout = 10 * time.Second defaultWSReadTimeout = 2 * time.Minute defaultWSWriteTimeout = 10 * time.Second + defaultCacheTtl = 1 * time.Hour maxRequestBodyLogLen = 2000 defaultMaxUpstreamBatchSize = 10 defaultRateLimitHeader = "X-Forwarded-For" From 08b38db21730b4713e43ecc9a9cf65045c6c8dac Mon Sep 17 00:00:00 2001 From: felipe <130432649+felipe-op@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:29:59 -0800 Subject: [PATCH 187/212] fix(proxyd): log initialization (#9785) * fix(proxyd): log initialization * update integration test as well --- proxyd/proxyd/cmd/proxyd/main.go | 4 ++-- proxyd/proxyd/integration_tests/util_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/proxyd/proxyd/cmd/proxyd/main.go b/proxyd/proxyd/cmd/proxyd/main.go index 36e0767..2f7579a 100644 --- a/proxyd/proxyd/cmd/proxyd/main.go +++ b/proxyd/proxyd/cmd/proxyd/main.go @@ -28,7 +28,7 @@ var ( func main() { // Set up logger with a default INFO level in case we fail to parse flags. // Otherwise the final critical log won't show what the parsing error was. - slog.SetDefault(slog.New(slog.NewJSONHandler( + log.SetDefault(log.NewLogger(slog.NewJSONHandler( os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))) log.Info("starting proxyd", "version", GitVersion, "commit", GitCommit, "date", GitDate) @@ -50,7 +50,7 @@ func main() { log.Warn("invalid server.log_level set: " + config.Server.LogLevel) } } - slog.SetDefault(slog.New(slog.NewJSONHandler( + log.SetDefault(log.NewLogger(slog.NewJSONHandler( os.Stdout, &slog.HandlerOptions{Level: logLevel}))) if config.Server.EnablePprof { diff --git a/proxyd/proxyd/integration_tests/util_test.go b/proxyd/proxyd/integration_tests/util_test.go index e2b1eb6..36edce1 100644 --- a/proxyd/proxyd/integration_tests/util_test.go +++ b/proxyd/proxyd/integration_tests/util_test.go @@ -186,6 +186,6 @@ func (h *ProxydWSClient) WriteControlMessage(msgType int, msg []byte) error { } func InitLogger() { - slog.SetDefault(slog.New( - log.NewTerminalHandlerWithLevel(os.Stdout, slog.LevelDebug, false))) + log.SetDefault(log.NewLogger(slog.NewJSONHandler( + os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))) } From 89cb5f50575f67bc2d3a885fac33b62c93976437 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:37:24 -0700 Subject: [PATCH 188/212] build(deps): bump google.golang.org/protobuf in /proxyd (#9851) Bumps google.golang.org/protobuf from 1.31.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> --- proxyd/proxyd/go.mod | 2 +- proxyd/proxyd/go.sum | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 9bd499b..d207332 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -81,6 +81,6 @@ require ( golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.15.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index c93d232..9b7fd5e 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -96,7 +96,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 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= @@ -105,7 +104,6 @@ github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs0 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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= @@ -273,9 +271,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ 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.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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= From fcf6e3323de9793a2d2783d18645323f99e92b19 Mon Sep 17 00:00:00 2001 From: felipe <130432649+felipe-op@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:26:39 -0700 Subject: [PATCH 189/212] feat(proxyd): use a specific redis instance for consensus_ha (#9877) --- proxyd/proxyd/config.go | 1 + proxyd/proxyd/proxyd.go | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index cd0cca1..3c622f8 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -123,6 +123,7 @@ type BackendGroupConfig struct { ConsensusHA bool `toml:"consensus_ha"` ConsensusHAHeartbeatInterval TOMLDuration `toml:"consensus_ha_heartbeat_interval"` ConsensusHALockPeriod TOMLDuration `toml:"consensus_ha_lock_period"` + ConsensusHARedis RedisConfig `toml:"consensus_ha_redis"` } type BackendGroupsConfig map[string]*BackendGroupConfig diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 7af47b1..b853429 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -335,8 +335,8 @@ func Start(config *Config) (*Server, func(), error) { var tracker ConsensusTracker if bgcfg.ConsensusHA { - if redisClient == nil { - log.Crit("cant start - consensus high availability requires redis") + if bgcfg.ConsensusHARedis.URL == "" { + log.Crit("must specify a consensus_ha_redis config when consensus_ha is true") } topts := make([]RedisConsensusTrackerOpt, 0) if bgcfg.ConsensusHALockPeriod > 0 { @@ -345,7 +345,11 @@ func Start(config *Config) (*Server, func(), error) { if bgcfg.ConsensusHAHeartbeatInterval > 0 { topts = append(topts, WithLockPeriod(time.Duration(bgcfg.ConsensusHAHeartbeatInterval))) } - tracker = NewRedisConsensusTracker(context.Background(), redisClient, bg, bg.Name, topts...) + consensusHARedisClient, err := NewRedisClient(bgcfg.ConsensusHARedis.URL) + if err != nil { + return nil, nil, err + } + tracker = NewRedisConsensusTracker(context.Background(), consensusHARedisClient, bg, bg.Name, topts...) copts = append(copts, WithTracker(tracker)) } From 94c236b467d5095e8d62b2e0cde31eda95bc03b8 Mon Sep 17 00:00:00 2001 From: felipe <130432649+felipe-op@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:26:59 -0700 Subject: [PATCH 190/212] feat(proxyd): add smoke test (#9875) --- proxyd/proxyd/cmd/proxyd/main.go | 6 +-- proxyd/proxyd/integration_tests/smoke_test.go | 51 +++++++++++++++++++ .../integration_tests/testdata/smoke.toml | 18 +++++++ proxyd/proxyd/proxyd.go | 6 +++ 4 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 proxyd/proxyd/integration_tests/smoke_test.go create mode 100644 proxyd/proxyd/integration_tests/testdata/smoke.toml diff --git a/proxyd/proxyd/cmd/proxyd/main.go b/proxyd/proxyd/cmd/proxyd/main.go index 2f7579a..3daa80b 100644 --- a/proxyd/proxyd/cmd/proxyd/main.go +++ b/proxyd/proxyd/cmd/proxyd/main.go @@ -28,8 +28,7 @@ var ( func main() { // Set up logger with a default INFO level in case we fail to parse flags. // Otherwise the final critical log won't show what the parsing error was. - log.SetDefault(log.NewLogger(slog.NewJSONHandler( - os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))) + proxyd.SetLogLevel(slog.LevelInfo) log.Info("starting proxyd", "version", GitVersion, "commit", GitCommit, "date", GitDate) @@ -50,8 +49,7 @@ func main() { log.Warn("invalid server.log_level set: " + config.Server.LogLevel) } } - log.SetDefault(log.NewLogger(slog.NewJSONHandler( - os.Stdout, &slog.HandlerOptions{Level: logLevel}))) + proxyd.SetLogLevel(logLevel) if config.Server.EnablePprof { log.Info("starting pprof", "addr", "0.0.0.0", "port", "6060") diff --git a/proxyd/proxyd/integration_tests/smoke_test.go b/proxyd/proxyd/integration_tests/smoke_test.go new file mode 100644 index 0000000..5fed757 --- /dev/null +++ b/proxyd/proxyd/integration_tests/smoke_test.go @@ -0,0 +1,51 @@ +package integration_tests + +import ( + "fmt" + "io" + "os" + "strings" + "testing" + + "github.com/ethereum-optimism/optimism/proxyd" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestInitProxyd(t *testing.T) { + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) + defer goodBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + config := ReadConfig("smoke") + + sysStdOut := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + proxyd.SetLogLevel(log.LevelInfo) + + defer func() { + w.Close() + out, _ := io.ReadAll(r) + require.True(t, strings.Contains(string(out), "started proxyd")) + require.True(t, strings.Contains(string(out), "shutting down proxyd")) + fmt.Println(string(out)) + os.Stdout = sysStdOut + }() + + _, shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + t.Run("initialization", func(t *testing.T) { + client := NewProxydClient("http://127.0.0.1:8545") + res, code, err := client.SendRPC(ethChainID, nil) + require.NoError(t, err) + require.Equal(t, 200, code) + require.NotNil(t, res) + }) + +} diff --git a/proxyd/proxyd/integration_tests/testdata/smoke.toml b/proxyd/proxyd/integration_tests/testdata/smoke.toml new file mode 100644 index 0000000..a2187a2 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/smoke.toml @@ -0,0 +1,18 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 + +[backends] +[backends.good] +rpc_url = "$GOOD_BACKEND_RPC_URL" +ws_url = "$GOOD_BACKEND_RPC_URL" + +[backend_groups] +[backend_groups.main] +backends = ["good"] + +[rpc_method_mappings] +eth_chainId = "main" + diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index b853429..0727efe 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -13,9 +13,15 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/redis/go-redis/v9" + "golang.org/x/exp/slog" "golang.org/x/sync/semaphore" ) +func SetLogLevel(logLevel slog.Leveler) { + log.SetDefault(log.NewLogger(slog.NewJSONHandler( + os.Stdout, &slog.HandlerOptions{Level: logLevel}))) +} + func Start(config *Config) (*Server, func(), error) { if len(config.Backends) == 0 { return nil, nil, errors.New("must define at least one backend") From 84a504dc398d707521e4ae5a32c17b1412a83e53 Mon Sep 17 00:00:00 2001 From: felipe <130432649+felipe-op@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:10:11 -0700 Subject: [PATCH 191/212] feat(proxyd): ha redis namespace should be both backend group name and the namespace itself (#9955) --- proxyd/proxyd/proxyd.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 0727efe..e703e9b 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -355,7 +355,8 @@ func Start(config *Config) (*Server, func(), error) { if err != nil { return nil, nil, err } - tracker = NewRedisConsensusTracker(context.Background(), consensusHARedisClient, bg, bg.Name, topts...) + ns := fmt.Sprintf("%s:%s", bgcfg.ConsensusHARedis.Namespace, bg.Name) + tracker = NewRedisConsensusTracker(context.Background(), consensusHARedisClient, bg, ns, topts...) copts = append(copts, WithTracker(tracker)) } From 82b11f53b2a5e9e23cb5f5a53fffaefc6e8b4594 Mon Sep 17 00:00:00 2001 From: Roberto Bayardo Date: Wed, 3 Apr 2024 19:15:15 -0700 Subject: [PATCH 192/212] 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 --- proxyd/proxyd/go.mod | 4 ++-- proxyd/proxyd/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index d207332..17e6e9f 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alicebob/miniredis v2.5.0+incompatible github.com/emirpasic/gods v1.18.1 - github.com/ethereum/go-ethereum v1.13.8 + github.com/ethereum/go-ethereum v1.13.14 github.com/go-redsync/redsync/v4 v4.10.0 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 @@ -78,7 +78,7 @@ require ( github.com/yusufpapurcu/wmi v1.2.3 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.15.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index 9b7fd5e..bd82a1a 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -62,8 +62,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 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/ethereum/go-ethereum v1.13.8 h1:1od+thJel3tM52ZUNQwvpYOeRHlbkVFZ5S8fhi0Lgsg= -github.com/ethereum/go-ethereum v1.13.8/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA= +github.com/ethereum/go-ethereum v1.13.14 h1:EwiY3FZP94derMCIam1iW4HFVrSgIcpsu0HwTQtm6CQ= +github.com/ethereum/go-ethereum v1.13.14/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -247,8 +247,8 @@ 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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= From 3a1b2633bb9b4a93aa8c0b96d4208fafb20d3dda Mon Sep 17 00:00:00 2001 From: sellskin Date: Wed, 10 Apr 2024 00:54:15 +0800 Subject: [PATCH 193/212] refactor: Optimize code by using built-in constants in the standard library (#9963) Signed-off-by: sellskin --- proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go b/proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go index 7f5e9b7..37074db 100644 --- a/proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go +++ b/proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go @@ -269,8 +269,7 @@ func TestSlidingWindow_CustomBucket(t *testing.T) { // ts is a convenient method that must parse a time.Time from a string in format `"2006-01-02 15:04:05"` func ts(s string) time.Time { - format := "2006-01-02 15:04:05" - t, err := time.Parse(format, s) + t, err := time.Parse(time.DateTime, s) if err != nil { panic(err) } From 094edc4fc6acfbd26c2cb6da330cb1c8d0828804 Mon Sep 17 00:00:00 2001 From: tdot Date: Thu, 11 Apr 2024 16:07:16 -0600 Subject: [PATCH 194/212] feat(proxyd): customise poller interval and enable cross origin check on web socket upgrader (#10107) * feat: poller interval arg * fix: websocket check origin * fix: check origin behind flag * fix: rename poller default interval --- proxyd/proxyd/config.go | 6 ++++-- proxyd/proxyd/consensus_poller.go | 14 +++++++++++--- proxyd/proxyd/proxyd.go | 13 ++++++++++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 3c622f8..4eed330 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -26,6 +26,7 @@ type ServerConfig struct { MaxRequestBodyLogLen int `toml:"max_request_body_log_len"` EnablePprof bool `toml:"enable_pprof"` EnableXServedByHeader bool `toml:"enable_served_by_header"` + AllowAllOrigins bool `toml:"allow_all_origins"` } type CacheConfig struct { @@ -111,8 +112,9 @@ type BackendGroupConfig struct { WeightedRouting bool `toml:"weighted_routing"` - ConsensusAware bool `toml:"consensus_aware"` - ConsensusAsyncHandler string `toml:"consensus_handler"` + ConsensusAware bool `toml:"consensus_aware"` + ConsensusAsyncHandler string `toml:"consensus_handler"` + ConsensusPollerInterval TOMLDuration `toml:"consensus_poller_interval"` ConsensusBanPeriod TOMLDuration `toml:"consensus_ban_period"` ConsensusMaxUpdateThreshold TOMLDuration `toml:"consensus_max_update_threshold"` diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index c72992c..79f27b5 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -14,7 +14,7 @@ import ( ) const ( - PollerInterval = 1 * time.Second + DefaultPollerInterval = 1 * time.Second ) type OnConsensusBroken func() @@ -40,6 +40,7 @@ type ConsensusPoller struct { maxUpdateThreshold time.Duration maxBlockLag uint64 maxBlockRange uint64 + interval time.Duration } type backendState struct { @@ -125,7 +126,7 @@ func (ah *PollerAsyncHandler) Init() { for _, be := range ah.cp.backendGroup.Backends { go func(be *Backend) { for { - timer := time.NewTimer(PollerInterval) + timer := time.NewTimer(ah.cp.interval) ah.cp.UpdateBackend(ah.ctx, be) select { @@ -141,7 +142,7 @@ func (ah *PollerAsyncHandler) Init() { // create the group consensus poller go func() { for { - timer := time.NewTimer(PollerInterval) + timer := time.NewTimer(ah.cp.interval) ah.cp.UpdateBackendGroupConsensus(ah.ctx) select { @@ -215,6 +216,12 @@ func WithMinPeerCount(minPeerCount uint64) ConsensusOpt { } } +func WithPollerInterval(interval time.Duration) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.interval = interval + } +} + func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller { ctx, cancelFunc := context.WithCancel(context.Background()) @@ -230,6 +237,7 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller maxUpdateThreshold: 30 * time.Second, maxBlockLag: 8, // 8*12 seconds = 96 seconds ~ 1.6 minutes minPeerCount: 3, + interval: DefaultPollerInterval, } for _, opt := range opts { diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index e703e9b..73a3117 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -272,6 +272,14 @@ func Start(config *Config) (*Server, func(), error) { return nil, nil, fmt.Errorf("error creating server: %w", err) } + // Enable to support browser websocket connections. + // See https://pkg.go.dev/github.com/gorilla/websocket#hdr-Origin_Considerations + if config.Server.AllowAllOrigins { + srv.upgrader.CheckOrigin = func(r *http.Request) bool { + return true + } + } + if config.Metrics.Enabled { addr := fmt.Sprintf("%s:%d", config.Metrics.Host, config.Metrics.Port) log.Info("starting metrics server", "addr", addr) @@ -338,6 +346,9 @@ func Start(config *Config) (*Server, func(), error) { if bgcfg.ConsensusMaxBlockRange > 0 { copts = append(copts, WithMaxBlockRange(bgcfg.ConsensusMaxBlockRange)) } + if bgcfg.ConsensusPollerInterval > 0 { + copts = append(copts, WithPollerInterval(time.Duration(bgcfg.ConsensusPollerInterval))) + } var tracker ConsensusTracker if bgcfg.ConsensusHA { @@ -349,7 +360,7 @@ func Start(config *Config) (*Server, func(), error) { topts = append(topts, WithLockPeriod(time.Duration(bgcfg.ConsensusHALockPeriod))) } if bgcfg.ConsensusHAHeartbeatInterval > 0 { - topts = append(topts, WithLockPeriod(time.Duration(bgcfg.ConsensusHAHeartbeatInterval))) + topts = append(topts, WithHeartbeatInterval(time.Duration(bgcfg.ConsensusHAHeartbeatInterval))) } consensusHARedisClient, err := NewRedisClient(bgcfg.ConsensusHARedis.URL) if err != nil { From f2e64d3c28b6de5d1475ac4bc10e180e8324534c Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Tue, 16 Apr 2024 21:42:13 -0500 Subject: [PATCH 195/212] Update proxyd blacklist status to match geth not found status (#10052) --- proxyd/proxyd/backend.go | 3 ++- proxyd/proxyd/integration_tests/testdata/testdata.txt | 4 ++-- proxyd/proxyd/integration_tests/validation_test.go | 2 +- proxyd/proxyd/integration_tests/ws_test.go | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index f00e951..81ec23c 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -30,6 +30,7 @@ import ( const ( JSONRPCVersion = "2.0" JSONRPCErrorInternal = -32000 + notFoundRpcError = -32601 ) var ( @@ -44,7 +45,7 @@ var ( HTTPErrorCode: 500, } ErrMethodNotWhitelisted = &RPCErr{ - Code: JSONRPCErrorInternal - 1, + Code: notFoundRpcError, Message: "rpc method is not whitelisted", HTTPErrorCode: 403, } diff --git a/proxyd/proxyd/integration_tests/testdata/testdata.txt b/proxyd/proxyd/integration_tests/testdata/testdata.txt index 14c9124..4bdd635 100644 --- a/proxyd/proxyd/integration_tests/testdata/testdata.txt +++ b/proxyd/proxyd/integration_tests/testdata/testdata.txt @@ -2,7 +2,7 @@ name|body|responseBody not json|not json|{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null} not json-rpc|{"foo":"bar"}|{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null} missing fields json-rpc|{"jsonrpc":"2.0"}|{"jsonrpc":"2.0","error":{"code":-32600,"message":"no method specified"},"id":null} -bad method json-rpc|{"jsonrpc":"2.0","method":"eth_notSendRawTransaction","id":1}|{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":1} +bad method json-rpc|{"jsonrpc":"2.0","method":"eth_notSendRawTransaction","id":1}|{"jsonrpc":"2.0","error":{"code":-32601,"message":"rpc method is not whitelisted"},"id":1} no transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"missing value for required argument 0"},"id":1} invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf6806872fcc650ad4e77e0629206426cd183d751e9ddcc8d5e77"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"rlp: value size exceeds available input length"},"id":1} invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x1234"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"transaction type not supported"},"id":1} @@ -11,4 +11,4 @@ valid transaction data - contract call|{"jsonrpc":"2.0","method":"eth_sendRawTra valid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} invalid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1} no chain id (pre eip-155) - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf865808609184e72a00082271094000000000000000000000000000000000000000001001ba0d937ddb66e7788f917864b8e6974cac376b091154db1c25ff8429a6e61016e74a054ced39349e7658b7efceccfabc461e02418eb510124377949cfae8ccf1831af"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} -batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1},{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1},{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":123}] +batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1},{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1},{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32601,"message":"rpc method is not whitelisted"},"id":123}] diff --git a/proxyd/proxyd/integration_tests/validation_test.go b/proxyd/proxyd/integration_tests/validation_test.go index 5f7d5ae..95cfc29 100644 --- a/proxyd/proxyd/integration_tests/validation_test.go +++ b/proxyd/proxyd/integration_tests/validation_test.go @@ -11,7 +11,7 @@ import ( ) const ( - notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted custom message"},"id":999}` + notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32601,"message":"rpc method is not whitelisted custom message"},"id":999}` parseErrResponse = `{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}` invalidJSONRPCVersionResponse = `{"error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null,"jsonrpc":"2.0"}` invalidIDResponse = `{"error":{"code":-32600,"message":"invalid ID"},"id":null,"jsonrpc":"2.0"}` diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/proxyd/integration_tests/ws_test.go index 1548f2a..d52cfab 100644 --- a/proxyd/proxyd/integration_tests/ws_test.go +++ b/proxyd/proxyd/integration_tests/ws_test.go @@ -101,7 +101,7 @@ func TestWS(t *testing.T) { { "blacklisted RPC", "}", - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"rpc method is not whitelisted\"},\"id\":1}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"rpc method is not whitelisted\"},\"id\":1}", "{\"id\": 1, \"method\": \"eth_whatever\", \"params\": []}", }, { From 0a2c79cafa45d5ce34e094c905d61b57efbf54ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 10:24:36 -0700 Subject: [PATCH 196/212] build(deps): bump github.com/ethereum/go-ethereum in /proxyd (#10413) Bumps [github.com/ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) from 1.13.14 to 1.13.15. - [Release notes](https://github.com/ethereum/go-ethereum/releases) - [Commits](https://github.com/ethereum/go-ethereum/compare/v1.13.14...v1.13.15) --- updated-dependencies: - dependency-name: github.com/ethereum/go-ethereum dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- proxyd/proxyd/go.mod | 2 +- proxyd/proxyd/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proxyd/proxyd/go.mod b/proxyd/proxyd/go.mod index 17e6e9f..088bf9b 100644 --- a/proxyd/proxyd/go.mod +++ b/proxyd/proxyd/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alicebob/miniredis v2.5.0+incompatible github.com/emirpasic/gods v1.18.1 - github.com/ethereum/go-ethereum v1.13.14 + github.com/ethereum/go-ethereum v1.13.15 github.com/go-redsync/redsync/v4 v4.10.0 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 diff --git a/proxyd/proxyd/go.sum b/proxyd/proxyd/go.sum index bd82a1a..11a684f 100644 --- a/proxyd/proxyd/go.sum +++ b/proxyd/proxyd/go.sum @@ -62,8 +62,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 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/ethereum/go-ethereum v1.13.14 h1:EwiY3FZP94derMCIam1iW4HFVrSgIcpsu0HwTQtm6CQ= -github.com/ethereum/go-ethereum v1.13.14/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= +github.com/ethereum/go-ethereum v1.13.15 h1:U7sSGYGo4SPjP6iNIifNoyIAiNjrmQkz6EwQG+/EZWo= +github.com/ethereum/go-ethereum v1.13.15/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= From 2f424fd5c6ca80a693b28eaa1fa1f8d77852e38d Mon Sep 17 00:00:00 2001 From: Hteev Oli Date: Thu, 9 May 2024 00:20:35 +0800 Subject: [PATCH 197/212] proxyd: remove unused error (#10444) --- proxyd/proxyd/server.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index 3fa3e00..527c2e6 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -156,11 +156,7 @@ func NewServer( overrideLims := make(map[string]FrontendRateLimiter) globalMethodLims := make(map[string]bool) for method, override := range rateLimitConfig.MethodOverrides { - var err error overrideLims[method] = limiterFactory(time.Duration(override.Interval), override.Limit, method) - if err != nil { - return nil, err - } if override.Global { globalMethodLims[method] = true From 584322fced4d46285fccd5baebf61abca1a9b20b Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 4 Jun 2024 18:19:54 -0500 Subject: [PATCH 198/212] feat: proxyd: enable consensus-proxyd be able to fallback to a different list of backends if the primary list is experiencing downtime. (#10668) Co-authored-by: Jacob Elias --- proxyd/proxyd/Makefile | 4 + proxyd/proxyd/backend.go | 31 +- proxyd/proxyd/config.go | 2 + proxyd/proxyd/consensus_poller.go | 60 ++- .../integration_tests/consensus_test.go | 3 + .../proxyd/integration_tests/fallback_test.go | 374 ++++++++++++++++++ .../integration_tests/testdata/fallback.toml | 31 ++ proxyd/proxyd/metrics.go | 26 ++ proxyd/proxyd/proxyd.go | 45 ++- 9 files changed, 564 insertions(+), 12 deletions(-) create mode 100644 proxyd/proxyd/integration_tests/fallback_test.go create mode 100644 proxyd/proxyd/integration_tests/testdata/fallback.toml diff --git a/proxyd/proxyd/Makefile b/proxyd/proxyd/Makefile index 049a23a..d9ffb57 100644 --- a/proxyd/proxyd/Makefile +++ b/proxyd/proxyd/Makefile @@ -19,3 +19,7 @@ test: lint: go vet ./... .PHONY: test + +test-fallback: + go test -v ./... -test.run ^TestFallback$ +.PHONY: test-fallback diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 81ec23c..802b94a 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -705,12 +705,35 @@ func sortBatchRPCResponse(req []*RPCReq, res []*RPCRes) { } type BackendGroup struct { - Name string - Backends []*Backend - WeightedRouting bool - Consensus *ConsensusPoller + Name string + Backends []*Backend + WeightedRouting bool + Consensus *ConsensusPoller + FallbackBackends map[string]bool } +func (bg *BackendGroup) Fallbacks() []*Backend { + fallbacks := []*Backend{} + for _, a := range bg.Backends { + if fallback, ok := bg.FallbackBackends[a.Name]; ok && fallback { + fallbacks = append(fallbacks, a) + } + } + return fallbacks +} + +func (bg *BackendGroup) Primaries() []*Backend { + primaries := []*Backend{} + for _, a := range bg.Backends { + fallback, ok := bg.FallbackBackends[a.Name] + if ok && !fallback { + primaries = append(primaries, a) + } + } + return primaries +} + +// NOTE: BackendGroup Forward contains the log for balancing with consensus aware func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, string, error) { if len(rpcReqs) == 0 { return nil, "", nil diff --git a/proxyd/proxyd/config.go b/proxyd/proxyd/config.go index 4eed330..4719a55 100644 --- a/proxyd/proxyd/config.go +++ b/proxyd/proxyd/config.go @@ -126,6 +126,8 @@ type BackendGroupConfig struct { ConsensusHAHeartbeatInterval TOMLDuration `toml:"consensus_ha_heartbeat_interval"` ConsensusHALockPeriod TOMLDuration `toml:"consensus_ha_lock_period"` ConsensusHARedis RedisConfig `toml:"consensus_ha_redis"` + + Fallbacks []string `toml:"fallbacks"` } type BackendGroupsConfig map[string]*BackendGroupConfig diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 79f27b5..64ac026 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -122,12 +122,38 @@ func NewPollerAsyncHandler(ctx context.Context, cp *ConsensusPoller) ConsensusAs } } func (ah *PollerAsyncHandler) Init() { - // create the individual backend pollers - for _, be := range ah.cp.backendGroup.Backends { + // create the individual backend pollers. + log.Info("total number of primary candidates", "primaries", len(ah.cp.backendGroup.Primaries())) + log.Info("total number of fallback candidates", "fallbacks", len(ah.cp.backendGroup.Fallbacks())) + + for _, be := range ah.cp.backendGroup.Primaries() { go func(be *Backend) { for { timer := time.NewTimer(ah.cp.interval) ah.cp.UpdateBackend(ah.ctx, be) + select { + case <-timer.C: + case <-ah.ctx.Done(): + timer.Stop() + return + } + } + }(be) + } + + for _, be := range ah.cp.backendGroup.Fallbacks() { + go func(be *Backend) { + for { + timer := time.NewTimer(ah.cp.interval) + + healthyCandidates := ah.cp.FilterCandidates(ah.cp.backendGroup.Primaries()) + + log.Info("number of healthy primary candidates", "healthy_candidates", len(healthyCandidates)) + if len(healthyCandidates) == 0 { + log.Info("zero healthy candidates, querying fallback backend", + "backend_name", be.Name) + ah.cp.UpdateBackend(ah.ctx, be) + } select { case <-timer.C: @@ -143,6 +169,7 @@ func (ah *PollerAsyncHandler) Init() { go func() { for { timer := time.NewTimer(ah.cp.interval) + log.Info("updating backend group consensus") ah.cp.UpdateBackendGroupConsensus(ah.ctx) select { @@ -609,6 +636,13 @@ func (cp *ConsensusPoller) getBackendState(be *Backend) *backendState { } } +func (cp *ConsensusPoller) GetLastUpdate(be *Backend) time.Time { + bs := cp.backendState[be] + defer bs.backendStateMux.Unlock() + bs.backendStateMux.Lock() + return bs.lastUpdate +} + func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync bool, latestBlockNumber hexutil.Uint64, latestBlockHash string, safeBlockNumber hexutil.Uint64, @@ -627,7 +661,21 @@ func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync return changed } -// getConsensusCandidates find out what backends are the candidates to be in the consensus group +// getConsensusCandidates will search for candidates in the primary group, +// if there are none it will search for candidates in he fallback group +func (cp *ConsensusPoller) getConsensusCandidates() map[*Backend]*backendState { + + healthyPrimaries := cp.FilterCandidates(cp.backendGroup.Primaries()) + + RecordHealthyCandidates(cp.backendGroup, len(healthyPrimaries)) + if len(healthyPrimaries) > 0 { + return healthyPrimaries + } + + return cp.FilterCandidates(cp.backendGroup.Fallbacks()) +} + +// filterCandidates find out what backends are the candidates to be in the consensus group // and create a copy of current their state // // a candidate is a serving node within the following conditions: @@ -637,10 +685,12 @@ func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync // - in sync // - updated recently // - not lagging latest block -func (cp *ConsensusPoller) getConsensusCandidates() map[*Backend]*backendState { +func (cp *ConsensusPoller) FilterCandidates(backends []*Backend) map[*Backend]*backendState { + candidates := make(map[*Backend]*backendState, len(cp.backendGroup.Backends)) - for _, be := range cp.backendGroup.Backends { + for _, be := range backends { + bs := cp.getBackendState(be) if be.forcedCandidate { candidates[be] = bs diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/proxyd/integration_tests/consensus_test.go index 1b37ef7..654b7a5 100644 --- a/proxyd/proxyd/integration_tests/consensus_test.go +++ b/proxyd/proxyd/integration_tests/consensus_test.go @@ -108,6 +108,9 @@ func TestConsensus(t *testing.T) { } override := func(node string, method string, block string, response string) { + if _, ok := nodes[node]; !ok { + t.Fatalf("node %s does not exist in the nodes map", node) + } nodes[node].handler.AddOverride(&ms.MethodTemplate{ Method: method, Block: block, diff --git a/proxyd/proxyd/integration_tests/fallback_test.go b/proxyd/proxyd/integration_tests/fallback_test.go new file mode 100644 index 0000000..c5b3e48 --- /dev/null +++ b/proxyd/proxyd/integration_tests/fallback_test.go @@ -0,0 +1,374 @@ +package integration_tests + +import ( + "context" + "fmt" + "net/http" + "os" + "path" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum-optimism/optimism/proxyd" + ms "github.com/ethereum-optimism/optimism/proxyd/tools/mockserver/handler" + "github.com/stretchr/testify/require" +) + +func setup_failover(t *testing.T) (map[string]nodeContext, *proxyd.BackendGroup, *ProxydHTTPClient, func(), []time.Time, []time.Time) { + // setup mock servers + node1 := NewMockBackend(nil) + node2 := NewMockBackend(nil) + + dir, err := os.Getwd() + require.NoError(t, err) + + responses := path.Join(dir, "testdata/consensus_responses.yml") + + h1 := ms.MockedHandler{ + Overrides: []*ms.MethodTemplate{}, + Autoload: true, + AutoloadFile: responses, + } + h2 := ms.MockedHandler{ + Overrides: []*ms.MethodTemplate{}, + Autoload: true, + AutoloadFile: responses, + } + + require.NoError(t, os.Setenv("NODE1_URL", node1.URL())) + require.NoError(t, os.Setenv("NODE2_URL", node2.URL())) + + node1.SetHandler(http.HandlerFunc(h1.Handler)) + node2.SetHandler(http.HandlerFunc(h2.Handler)) + + // setup proxyd + config := ReadConfig("fallback") + svr, shutdown, err := proxyd.Start(config) + require.NoError(t, err) + + // expose the proxyd client + client := NewProxydClient("http://127.0.0.1:8545") + + // expose the backend group + bg := svr.BackendGroups["node"] + require.NotNil(t, bg) + require.NotNil(t, bg.Consensus) + require.Equal(t, 2, len(bg.Backends)) // should match config + + // convenient mapping to access the nodes by name + nodes := map[string]nodeContext{ + "normal": { + mockBackend: node1, + backend: bg.Backends[0], + handler: &h1, + }, + "fallback": { + mockBackend: node2, + backend: bg.Backends[1], + handler: &h2, + }, + } + normalTimestamps := []time.Time{} + fallbackTimestamps := []time.Time{} + + return nodes, bg, client, shutdown, normalTimestamps, fallbackTimestamps +} + +func TestFallback(t *testing.T) { + nodes, bg, client, shutdown, normalTimestamps, fallbackTimestamps := setup_failover(t) + defer nodes["normal"].mockBackend.Close() + defer nodes["fallback"].mockBackend.Close() + defer shutdown() + + ctx := context.Background() + + // Use Update to Advance the Candidate iteration + update := func() { + for _, be := range bg.Primaries() { + bg.Consensus.UpdateBackend(ctx, be) + } + + for _, be := range bg.Fallbacks() { + healthyCandidates := bg.Consensus.FilterCandidates(bg.Primaries()) + if len(healthyCandidates) == 0 { + bg.Consensus.UpdateBackend(ctx, be) + } + } + + bg.Consensus.UpdateBackendGroupConsensus(ctx) + } + + override := func(node string, method string, block string, response string) { + if _, ok := nodes[node]; !ok { + t.Fatalf("node %s does not exist in the nodes map", node) + } + nodes[node].handler.AddOverride(&ms.MethodTemplate{ + Method: method, + Block: block, + Response: response, + }) + } + + overrideBlock := func(node string, blockRequest string, blockResponse string) { + override(node, + "eth_getBlockByNumber", + blockRequest, + buildResponse(map[string]string{ + "number": blockResponse, + "hash": "hash_" + blockResponse, + })) + } + + overrideBlockHash := func(node string, blockRequest string, number string, hash string) { + override(node, + "eth_getBlockByNumber", + blockRequest, + buildResponse(map[string]string{ + "number": number, + "hash": hash, + })) + } + + overridePeerCount := func(node string, count int) { + override(node, "net_peerCount", "", buildResponse(hexutil.Uint64(count).String())) + } + + overrideNotInSync := func(node string) { + override(node, "eth_syncing", "", buildResponse(map[string]string{ + "startingblock": "0x0", + "currentblock": "0x0", + "highestblock": "0x100", + })) + } + + containsNode := func(backends []*proxyd.Backend, name string) bool { + for _, be := range backends { + // Note: Currently checks for name but would like to expose fallback better + if be.Name == name { + return true + } + } + return false + } + + // TODO: Improvement instead of simple array, + // ensure normal and backend are returned in strict order + recordLastUpdates := func(backends []*proxyd.Backend) []time.Time { + lastUpdated := []time.Time{} + for _, be := range backends { + lastUpdated = append(lastUpdated, bg.Consensus.GetLastUpdate(be)) + } + return lastUpdated + } + + // convenient methods to manipulate state and mock responses + reset := func() { + for _, node := range nodes { + node.handler.ResetOverrides() + node.mockBackend.Reset() + } + bg.Consensus.ClearListeners() + bg.Consensus.Reset() + + normalTimestamps = []time.Time{} + fallbackTimestamps = []time.Time{} + } + + /* + triggerFirstNormalFailure: will trigger consensus group into fallback mode + old consensus group should be returned one time, and fallback group should be enabled + Fallback will be returned subsequent update + */ + triggerFirstNormalFailure := func() { + overridePeerCount("normal", 0) + update() + require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) + require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + nodes["fallback"].mockBackend.Reset() + } + + t.Run("Test fallback Mode will not be exited, unless state changes", func(t *testing.T) { + reset() + triggerFirstNormalFailure() + for i := 0; i < 10; i++ { + update() + require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) + require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + } + }) + + t.Run("Test Healthy mode will not be exited unless state changes", func(t *testing.T) { + reset() + for i := 0; i < 10; i++ { + update() + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) + require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) + + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) + + require.Equal(t, 200, statusCode) + require.Nil(t, err, "error not nil") + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + } + // TODO: Remove these, just here so compiler doesn't complain + overrideNotInSync("normal") + overrideBlock("normal", "safe", "0xb1") + overrideBlockHash("fallback", "0x102", "0x102", "wrong_hash") + }) + + t.Run("trigger normal failure, subsequent update return failover in consensus group, and fallback mode enabled", func(t *testing.T) { + reset() + triggerFirstNormalFailure() + update() + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) + require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) + }) + + t.Run("trigger healthy -> fallback, update -> healthy", func(t *testing.T) { + reset() + update() + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) + require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) + + triggerFirstNormalFailure() + update() + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) + require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) + + overridePeerCount("normal", 5) + update() + require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) + require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) + require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) + }) + + t.Run("Ensure fallback is not updated when in normal mode", func(t *testing.T) { + reset() + for i := 0; i < 10; i++ { + update() + ts := recordLastUpdates(bg.Backends) + normalTimestamps = append(normalTimestamps, ts[0]) + fallbackTimestamps = append(fallbackTimestamps, ts[1]) + + require.False(t, normalTimestamps[i].IsZero()) + require.True(t, fallbackTimestamps[i].IsZero()) + + require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) + require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) + + // consensus at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + } + }) + + /* + Set Normal backend to Fail -> both backends should be updated + */ + t.Run("Ensure both nodes are quieried in fallback mode", func(t *testing.T) { + reset() + triggerFirstNormalFailure() + for i := 0; i < 10; i++ { + update() + ts := recordLastUpdates(bg.Backends) + normalTimestamps = append(normalTimestamps, ts[0]) + fallbackTimestamps = append(fallbackTimestamps, ts[1]) + + // Both Nodes should be updated again + require.False(t, normalTimestamps[i].IsZero()) + require.False(t, fallbackTimestamps[i].IsZero(), + fmt.Sprintf("Error: Fallback timestamp: %v was not queried on iteratio %d", fallbackTimestamps[i], i), + ) + if i > 0 { + require.Greater(t, normalTimestamps[i], normalTimestamps[i-1]) + require.Greater(t, fallbackTimestamps[i], fallbackTimestamps[i-1]) + } + } + }) + + t.Run("Ensure both nodes are quieried in fallback mode", func(t *testing.T) { + reset() + triggerFirstNormalFailure() + for i := 0; i < 10; i++ { + update() + ts := recordLastUpdates(bg.Backends) + normalTimestamps = append(normalTimestamps, ts[0]) + fallbackTimestamps = append(fallbackTimestamps, ts[1]) + + // Both Nodes should be updated again + require.False(t, normalTimestamps[i].IsZero()) + require.False(t, fallbackTimestamps[i].IsZero(), + fmt.Sprintf("Error: Fallback timestamp: %v was not queried on iteratio %d", fallbackTimestamps[i], i), + ) + if i > 0 { + require.Greater(t, normalTimestamps[i], normalTimestamps[i-1]) + require.Greater(t, fallbackTimestamps[i], fallbackTimestamps[i-1]) + } + } + }) + t.Run("Healthy -> Fallback -> Healthy with timestamps", func(t *testing.T) { + reset() + for i := 0; i < 10; i++ { + update() + ts := recordLastUpdates(bg.Backends) + normalTimestamps = append(normalTimestamps, ts[0]) + fallbackTimestamps = append(fallbackTimestamps, ts[1]) + + // Normal is queried, fallback is not + require.False(t, normalTimestamps[i].IsZero()) + require.True(t, fallbackTimestamps[i].IsZero(), + fmt.Sprintf("Error: Fallback timestamp: %v was not queried on iteratio %d", fallbackTimestamps[i], i), + ) + if i > 0 { + require.Greater(t, normalTimestamps[i], normalTimestamps[i-1]) + // Fallbacks should be zeros + require.Equal(t, fallbackTimestamps[i], fallbackTimestamps[i-1]) + } + } + + offset := 10 + triggerFirstNormalFailure() + for i := 0; i < 10; i++ { + update() + ts := recordLastUpdates(bg.Backends) + normalTimestamps = append(normalTimestamps, ts[0]) + fallbackTimestamps = append(fallbackTimestamps, ts[1]) + + // Both Nodes should be updated again + require.False(t, normalTimestamps[i+offset].IsZero()) + require.False(t, fallbackTimestamps[i+offset].IsZero()) + + require.Greater(t, normalTimestamps[i+offset], normalTimestamps[i+offset-1]) + require.Greater(t, fallbackTimestamps[i+offset], fallbackTimestamps[i+offset-1]) + } + + overridePeerCount("normal", 5) + offset = 20 + for i := 0; i < 10; i++ { + update() + ts := recordLastUpdates(bg.Backends) + normalTimestamps = append(normalTimestamps, ts[0]) + fallbackTimestamps = append(fallbackTimestamps, ts[1]) + + // Normal Node will be updated + require.False(t, normalTimestamps[i+offset].IsZero()) + require.Greater(t, normalTimestamps[i+offset], normalTimestamps[i+offset-1]) + + // fallback should not be updating + if offset+i > 21 { + require.Equal(t, fallbackTimestamps[i+offset], fallbackTimestamps[i+offset-1]) + } + } + }) +} diff --git a/proxyd/proxyd/integration_tests/testdata/fallback.toml b/proxyd/proxyd/integration_tests/testdata/fallback.toml new file mode 100644 index 0000000..c801ca3 --- /dev/null +++ b/proxyd/proxyd/integration_tests/testdata/fallback.toml @@ -0,0 +1,31 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 +max_degraded_latency_threshold = "30ms" + +[backends] +[backends.normal] +rpc_url = "$NODE1_URL" + +[backends.fallback] +rpc_url = "$NODE2_URL" + +[backend_groups] +[backend_groups.node] +backends = ["normal", "fallback"] +consensus_aware = true +consensus_handler = "noop" # allow more control over the consensus poller for tests +consensus_ban_period = "1m" +consensus_max_update_threshold = "2m" +consensus_max_block_lag = 8 +consensus_min_peer_count = 4 +fallbacks = ["fallback"] + +[rpc_method_mappings] +eth_call = "node" +eth_chainId = "node" +eth_blockNumber = "node" +eth_getBlockByNumber = "node" +consensus_getReceipts = "node" diff --git a/proxyd/proxyd/metrics.go b/proxyd/proxyd/metrics.go index 90a79ab..4046af0 100644 --- a/proxyd/proxyd/metrics.go +++ b/proxyd/proxyd/metrics.go @@ -410,6 +410,24 @@ var ( }, []string{ "backend_name", }) + + healthyPrimaryCandidates = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "healthy_candidates", + Help: "Record the number of healthy primary candidates", + }, []string{ + "backend_group_name", + }) + + backendGroupFallbackBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_group_fallback_backenend", + Help: "Bool gauge for if a backend is a fallback for a backend group", + }, []string{ + "backend_group", + "backend_name", + "fallback", + }) ) func RecordRedisError(source string) { @@ -541,6 +559,10 @@ func RecordConsensusBackendBanned(b *Backend, banned bool) { consensusBannedBackends.WithLabelValues(b.Name).Set(boolToFloat64(banned)) } +func RecordHealthyCandidates(b *BackendGroup, candidates int) { + healthyPrimaryCandidates.WithLabelValues(b.Name).Set(float64(candidates)) +} + func RecordConsensusBackendPeerCount(b *Backend, peerCount uint64) { consensusPeerCountBackend.WithLabelValues(b.Name).Set(float64(peerCount)) } @@ -567,6 +589,10 @@ func RecordBackendNetworkErrorRateSlidingWindow(b *Backend, rate float64) { networkErrorRateBackend.WithLabelValues(b.Name).Set(rate) } +func RecordBackendGroupFallbacks(bg *BackendGroup, name string, fallback bool) { + backendGroupFallbackBackend.WithLabelValues(bg.Name, name, strconv.FormatBool(fallback)).Set(boolToFloat64(fallback)) +} + func boolToFloat64(b bool) float64 { if b { return 1 diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd/proxyd.go index 73a3117..402909b 100644 --- a/proxyd/proxyd/proxyd.go +++ b/proxyd/proxyd/proxyd.go @@ -187,17 +187,47 @@ func Start(config *Config) (*Server, func(), error) { backendGroups := make(map[string]*BackendGroup) for bgName, bg := range config.BackendGroups { backends := make([]*Backend, 0) + fallbackBackends := make(map[string]bool) + fallbackCount := 0 for _, bName := range bg.Backends { if backendsByName[bName] == nil { return nil, nil, fmt.Errorf("backend %s is not defined", bName) } backends = append(backends, backendsByName[bName]) + + for _, fb := range bg.Fallbacks { + if bName == fb { + fallbackBackends[bName] = true + log.Info("configured backend as fallback", + "backend_name", bName, + "backend_group", bgName, + ) + fallbackCount++ + } + } + + if _, ok := fallbackBackends[bName]; !ok { + fallbackBackends[bName] = false + log.Info("configured backend as primary", + "backend_name", bName, + "backend_group", bgName, + ) + } + } + + if fallbackCount != len(bg.Fallbacks) { + return nil, nil, + fmt.Errorf( + "error: number of fallbacks instantiated (%d) did not match configured (%d) for backend group %s", + fallbackCount, len(bg.Fallbacks), bgName, + ) } backendGroups[bgName] = &BackendGroup{ - Name: bgName, - Backends: backends, - WeightedRouting: bg.WeightedRouting, + Name: bgName, + Backends: backends, + WeightedRouting: bg.WeightedRouting, + FallbackBackends: fallbackBackends, } } @@ -350,6 +380,15 @@ func Start(config *Config) (*Server, func(), error) { copts = append(copts, WithPollerInterval(time.Duration(bgcfg.ConsensusPollerInterval))) } + for _, be := range bgcfg.Backends { + if fallback, ok := bg.FallbackBackends[be]; !ok { + log.Crit("error backend not found in backend fallback configurations", "backend_name", be) + } else { + log.Debug("configuring new backend for group", "backend_group", bgName, "backend_name", be, "fallback", fallback) + RecordBackendGroupFallbacks(bg, be, fallback) + } + } + var tracker ConsensusTracker if bgcfg.ConsensusHA { if bgcfg.ConsensusHARedis.URL == "" { From cbcf9d3c3b6fa3383b14ab41557c4d4d078d680f Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Mon, 10 Jun 2024 15:04:47 -0500 Subject: [PATCH 199/212] feat: misc logging improvements for fallback mode (#10775) --- proxyd/proxyd/consensus_poller.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/proxyd/consensus_poller.go index 64ac026..90af41d 100644 --- a/proxyd/proxyd/consensus_poller.go +++ b/proxyd/proxyd/consensus_poller.go @@ -150,7 +150,7 @@ func (ah *PollerAsyncHandler) Init() { log.Info("number of healthy primary candidates", "healthy_candidates", len(healthyCandidates)) if len(healthyCandidates) == 0 { - log.Info("zero healthy candidates, querying fallback backend", + log.Debug("zero healthy candidates, querying fallback backend", "backend_name", be.Name) ah.cp.UpdateBackend(ah.ctx, be) } @@ -703,6 +703,11 @@ func (cp *ConsensusPoller) FilterCandidates(backends []*Backend) map[*Backend]*b continue } if !be.skipPeerCountCheck && bs.peerCount < cp.minPeerCount { + log.Debug("backend peer count too low for inclusion in consensus", + "backend_name", be.Name, + "peer_count", bs.peerCount, + "min_peer_count", cp.minPeerCount, + ) continue } if !bs.inSync { From c2101a4fbbf9b2163c2fb8c5084e72b6bcd52b98 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 14:10:20 -0500 Subject: [PATCH 200/212] v1 circle-ci configs for proxyd --- .circleci/config.yml | 1 + .circleci/continue_config.yml | 27 +++++++++++++++++++ proxyd/{proxyd => }/.gitignore | 0 proxyd/{proxyd => }/CHANGELOG.md | 0 proxyd/{proxyd => }/Dockerfile | 0 proxyd/{proxyd => }/Dockerfile.ignore | 0 proxyd/{proxyd => }/Makefile | 0 proxyd/{proxyd => }/README.md | 0 proxyd/{proxyd => }/backend.go | 0 proxyd/{proxyd => }/backend_test.go | 0 proxyd/{proxyd => }/cache.go | 0 proxyd/{proxyd => }/cache_test.go | 0 proxyd/{proxyd => }/cmd/proxyd/main.go | 0 proxyd/{proxyd => }/config.go | 0 proxyd/{proxyd => }/consensus_poller.go | 0 proxyd/{proxyd => }/consensus_tracker.go | 0 proxyd/{proxyd => }/entrypoint.sh | 0 proxyd/{proxyd => }/errors.go | 0 proxyd/{proxyd => }/example.config.toml | 0 proxyd/{proxyd => }/frontend_rate_limiter.go | 0 .../frontend_rate_limiter_test.go | 0 proxyd/{proxyd => }/go.mod | 0 proxyd/{proxyd => }/go.sum | 0 .../integration_tests/batch_timeout_test.go | 0 .../integration_tests/batching_test.go | 0 .../integration_tests/caching_test.go | 0 .../integration_tests/consensus_test.go | 0 .../integration_tests/failover_test.go | 0 .../integration_tests/fallback_test.go | 0 .../integration_tests/max_rpc_conns_test.go | 0 .../integration_tests/mock_backend_test.go | 0 .../integration_tests/rate_limit_test.go | 0 .../sender_rate_limit_test.go | 0 .../integration_tests/smoke_test.go | 0 .../testdata/batch_timeout.toml | 0 .../integration_tests/testdata/batching.toml | 0 .../integration_tests/testdata/caching.toml | 0 .../integration_tests/testdata/consensus.toml | 0 .../testdata/consensus_responses.yml | 0 .../integration_tests/testdata/failover.toml | 0 .../integration_tests/testdata/fallback.toml | 0 .../testdata/frontend_rate_limit.toml | 0 .../testdata/max_rpc_conns.toml | 0 .../testdata/out_of_service_interval.toml | 0 .../integration_tests/testdata/retries.toml | 0 .../testdata/sender_rate_limit.toml | 0 .../testdata/size_limits.toml | 0 .../integration_tests/testdata/smoke.toml | 0 .../integration_tests/testdata/testdata.txt | 0 .../integration_tests/testdata/whitelist.toml | 0 .../integration_tests/testdata/ws.toml | 0 .../integration_tests/util_test.go | 0 .../integration_tests/validation_test.go | 0 .../{proxyd => }/integration_tests/ws_test.go | 0 proxyd/{proxyd => }/methods.go | 0 proxyd/{proxyd => }/metrics.go | 0 .../pkg/avg-sliding-window/sliding.go | 0 .../pkg/avg-sliding-window/sliding_test.go | 0 proxyd/{proxyd => }/proxyd.go | 0 proxyd/{proxyd => }/reader.go | 0 proxyd/{proxyd => }/reader_test.go | 0 proxyd/{proxyd => }/redis.go | 0 proxyd/{proxyd => }/rewriter.go | 0 proxyd/{proxyd => }/rewriter_test.go | 0 proxyd/{proxyd => }/rpc.go | 0 proxyd/{proxyd => }/rpc_test.go | 0 proxyd/{proxyd => }/server.go | 0 proxyd/{proxyd => }/string_set.go | 0 proxyd/{proxyd => }/tls.go | 0 .../tools/mockserver/handler/handler.go | 0 proxyd/{proxyd => }/tools/mockserver/main.go | 0 .../{proxyd => }/tools/mockserver/node1.yml | 0 .../{proxyd => }/tools/mockserver/node2.yml | 0 73 files changed, 28 insertions(+) rename proxyd/{proxyd => }/.gitignore (100%) rename proxyd/{proxyd => }/CHANGELOG.md (100%) rename proxyd/{proxyd => }/Dockerfile (100%) rename proxyd/{proxyd => }/Dockerfile.ignore (100%) rename proxyd/{proxyd => }/Makefile (100%) rename proxyd/{proxyd => }/README.md (100%) rename proxyd/{proxyd => }/backend.go (100%) rename proxyd/{proxyd => }/backend_test.go (100%) rename proxyd/{proxyd => }/cache.go (100%) rename proxyd/{proxyd => }/cache_test.go (100%) rename proxyd/{proxyd => }/cmd/proxyd/main.go (100%) rename proxyd/{proxyd => }/config.go (100%) rename proxyd/{proxyd => }/consensus_poller.go (100%) rename proxyd/{proxyd => }/consensus_tracker.go (100%) rename proxyd/{proxyd => }/entrypoint.sh (100%) rename proxyd/{proxyd => }/errors.go (100%) rename proxyd/{proxyd => }/example.config.toml (100%) rename proxyd/{proxyd => }/frontend_rate_limiter.go (100%) rename proxyd/{proxyd => }/frontend_rate_limiter_test.go (100%) rename proxyd/{proxyd => }/go.mod (100%) rename proxyd/{proxyd => }/go.sum (100%) rename proxyd/{proxyd => }/integration_tests/batch_timeout_test.go (100%) rename proxyd/{proxyd => }/integration_tests/batching_test.go (100%) rename proxyd/{proxyd => }/integration_tests/caching_test.go (100%) rename proxyd/{proxyd => }/integration_tests/consensus_test.go (100%) rename proxyd/{proxyd => }/integration_tests/failover_test.go (100%) rename proxyd/{proxyd => }/integration_tests/fallback_test.go (100%) rename proxyd/{proxyd => }/integration_tests/max_rpc_conns_test.go (100%) rename proxyd/{proxyd => }/integration_tests/mock_backend_test.go (100%) rename proxyd/{proxyd => }/integration_tests/rate_limit_test.go (100%) rename proxyd/{proxyd => }/integration_tests/sender_rate_limit_test.go (100%) rename proxyd/{proxyd => }/integration_tests/smoke_test.go (100%) rename proxyd/{proxyd => }/integration_tests/testdata/batch_timeout.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/batching.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/caching.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/consensus.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/consensus_responses.yml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/failover.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/fallback.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/frontend_rate_limit.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/max_rpc_conns.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/out_of_service_interval.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/retries.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/sender_rate_limit.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/size_limits.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/smoke.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/testdata.txt (100%) rename proxyd/{proxyd => }/integration_tests/testdata/whitelist.toml (100%) rename proxyd/{proxyd => }/integration_tests/testdata/ws.toml (100%) rename proxyd/{proxyd => }/integration_tests/util_test.go (100%) rename proxyd/{proxyd => }/integration_tests/validation_test.go (100%) rename proxyd/{proxyd => }/integration_tests/ws_test.go (100%) rename proxyd/{proxyd => }/methods.go (100%) rename proxyd/{proxyd => }/metrics.go (100%) rename proxyd/{proxyd => }/pkg/avg-sliding-window/sliding.go (100%) rename proxyd/{proxyd => }/pkg/avg-sliding-window/sliding_test.go (100%) rename proxyd/{proxyd => }/proxyd.go (100%) rename proxyd/{proxyd => }/reader.go (100%) rename proxyd/{proxyd => }/reader_test.go (100%) rename proxyd/{proxyd => }/redis.go (100%) rename proxyd/{proxyd => }/rewriter.go (100%) rename proxyd/{proxyd => }/rewriter_test.go (100%) rename proxyd/{proxyd => }/rpc.go (100%) rename proxyd/{proxyd => }/rpc_test.go (100%) rename proxyd/{proxyd => }/server.go (100%) rename proxyd/{proxyd => }/string_set.go (100%) rename proxyd/{proxyd => }/tls.go (100%) rename proxyd/{proxyd => }/tools/mockserver/handler/handler.go (100%) rename proxyd/{proxyd => }/tools/mockserver/main.go (100%) rename proxyd/{proxyd => }/tools/mockserver/node1.yml (100%) rename proxyd/{proxyd => }/tools/mockserver/node2.yml (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7ac83a4..6853d9d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,6 +17,7 @@ workflows: mapping: | op-conductor-mon/.* run-build-op-conductor-mon true op-ufm/.* run-build-op-ufm true + proxyd/.* run-build-proxyd true .circleci/.* run-all true .github/.* run-all true diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index adcc7af..1450834 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -13,6 +13,9 @@ parameters: run-build-op-ufm: type: boolean default: false + run-build-proxyd: + type: boolean + default: false run-all: type: boolean default: false @@ -388,3 +391,27 @@ workflows: - oplabs-gcr requires: - op-ufm-docker-build + op-proxyd: + when: + or: [<< pipeline.parameters.run-build-proxyd >>, << pipeline.parameters.run-all >>] + jobs: + - go-lint: + name: proxyd-lint + module: proxyd + - go-test: + name: proxyd-tests + module: proxyd + - docker-build: + name: proxyd-docker-build + docker_file: proxyd/Dockerfile + docker_name: proxyd + docker_tags: <>,<> + docker_context: . + - docker-publish: + name: proxyd-docker-publish + docker_name: proxyd + docker_tags: <>,<> + context: + - oplabs-gcr + requires: + - proxyd-docker-build diff --git a/proxyd/proxyd/.gitignore b/proxyd/.gitignore similarity index 100% rename from proxyd/proxyd/.gitignore rename to proxyd/.gitignore diff --git a/proxyd/proxyd/CHANGELOG.md b/proxyd/CHANGELOG.md similarity index 100% rename from proxyd/proxyd/CHANGELOG.md rename to proxyd/CHANGELOG.md diff --git a/proxyd/proxyd/Dockerfile b/proxyd/Dockerfile similarity index 100% rename from proxyd/proxyd/Dockerfile rename to proxyd/Dockerfile diff --git a/proxyd/proxyd/Dockerfile.ignore b/proxyd/Dockerfile.ignore similarity index 100% rename from proxyd/proxyd/Dockerfile.ignore rename to proxyd/Dockerfile.ignore diff --git a/proxyd/proxyd/Makefile b/proxyd/Makefile similarity index 100% rename from proxyd/proxyd/Makefile rename to proxyd/Makefile diff --git a/proxyd/proxyd/README.md b/proxyd/README.md similarity index 100% rename from proxyd/proxyd/README.md rename to proxyd/README.md diff --git a/proxyd/proxyd/backend.go b/proxyd/backend.go similarity index 100% rename from proxyd/proxyd/backend.go rename to proxyd/backend.go diff --git a/proxyd/proxyd/backend_test.go b/proxyd/backend_test.go similarity index 100% rename from proxyd/proxyd/backend_test.go rename to proxyd/backend_test.go diff --git a/proxyd/proxyd/cache.go b/proxyd/cache.go similarity index 100% rename from proxyd/proxyd/cache.go rename to proxyd/cache.go diff --git a/proxyd/proxyd/cache_test.go b/proxyd/cache_test.go similarity index 100% rename from proxyd/proxyd/cache_test.go rename to proxyd/cache_test.go diff --git a/proxyd/proxyd/cmd/proxyd/main.go b/proxyd/cmd/proxyd/main.go similarity index 100% rename from proxyd/proxyd/cmd/proxyd/main.go rename to proxyd/cmd/proxyd/main.go diff --git a/proxyd/proxyd/config.go b/proxyd/config.go similarity index 100% rename from proxyd/proxyd/config.go rename to proxyd/config.go diff --git a/proxyd/proxyd/consensus_poller.go b/proxyd/consensus_poller.go similarity index 100% rename from proxyd/proxyd/consensus_poller.go rename to proxyd/consensus_poller.go diff --git a/proxyd/proxyd/consensus_tracker.go b/proxyd/consensus_tracker.go similarity index 100% rename from proxyd/proxyd/consensus_tracker.go rename to proxyd/consensus_tracker.go diff --git a/proxyd/proxyd/entrypoint.sh b/proxyd/entrypoint.sh similarity index 100% rename from proxyd/proxyd/entrypoint.sh rename to proxyd/entrypoint.sh diff --git a/proxyd/proxyd/errors.go b/proxyd/errors.go similarity index 100% rename from proxyd/proxyd/errors.go rename to proxyd/errors.go diff --git a/proxyd/proxyd/example.config.toml b/proxyd/example.config.toml similarity index 100% rename from proxyd/proxyd/example.config.toml rename to proxyd/example.config.toml diff --git a/proxyd/proxyd/frontend_rate_limiter.go b/proxyd/frontend_rate_limiter.go similarity index 100% rename from proxyd/proxyd/frontend_rate_limiter.go rename to proxyd/frontend_rate_limiter.go diff --git a/proxyd/proxyd/frontend_rate_limiter_test.go b/proxyd/frontend_rate_limiter_test.go similarity index 100% rename from proxyd/proxyd/frontend_rate_limiter_test.go rename to proxyd/frontend_rate_limiter_test.go diff --git a/proxyd/proxyd/go.mod b/proxyd/go.mod similarity index 100% rename from proxyd/proxyd/go.mod rename to proxyd/go.mod diff --git a/proxyd/proxyd/go.sum b/proxyd/go.sum similarity index 100% rename from proxyd/proxyd/go.sum rename to proxyd/go.sum diff --git a/proxyd/proxyd/integration_tests/batch_timeout_test.go b/proxyd/integration_tests/batch_timeout_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/batch_timeout_test.go rename to proxyd/integration_tests/batch_timeout_test.go diff --git a/proxyd/proxyd/integration_tests/batching_test.go b/proxyd/integration_tests/batching_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/batching_test.go rename to proxyd/integration_tests/batching_test.go diff --git a/proxyd/proxyd/integration_tests/caching_test.go b/proxyd/integration_tests/caching_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/caching_test.go rename to proxyd/integration_tests/caching_test.go diff --git a/proxyd/proxyd/integration_tests/consensus_test.go b/proxyd/integration_tests/consensus_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/consensus_test.go rename to proxyd/integration_tests/consensus_test.go diff --git a/proxyd/proxyd/integration_tests/failover_test.go b/proxyd/integration_tests/failover_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/failover_test.go rename to proxyd/integration_tests/failover_test.go diff --git a/proxyd/proxyd/integration_tests/fallback_test.go b/proxyd/integration_tests/fallback_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/fallback_test.go rename to proxyd/integration_tests/fallback_test.go diff --git a/proxyd/proxyd/integration_tests/max_rpc_conns_test.go b/proxyd/integration_tests/max_rpc_conns_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/max_rpc_conns_test.go rename to proxyd/integration_tests/max_rpc_conns_test.go diff --git a/proxyd/proxyd/integration_tests/mock_backend_test.go b/proxyd/integration_tests/mock_backend_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/mock_backend_test.go rename to proxyd/integration_tests/mock_backend_test.go diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/integration_tests/rate_limit_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/rate_limit_test.go rename to proxyd/integration_tests/rate_limit_test.go diff --git a/proxyd/proxyd/integration_tests/sender_rate_limit_test.go b/proxyd/integration_tests/sender_rate_limit_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/sender_rate_limit_test.go rename to proxyd/integration_tests/sender_rate_limit_test.go diff --git a/proxyd/proxyd/integration_tests/smoke_test.go b/proxyd/integration_tests/smoke_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/smoke_test.go rename to proxyd/integration_tests/smoke_test.go diff --git a/proxyd/proxyd/integration_tests/testdata/batch_timeout.toml b/proxyd/integration_tests/testdata/batch_timeout.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/batch_timeout.toml rename to proxyd/integration_tests/testdata/batch_timeout.toml diff --git a/proxyd/proxyd/integration_tests/testdata/batching.toml b/proxyd/integration_tests/testdata/batching.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/batching.toml rename to proxyd/integration_tests/testdata/batching.toml diff --git a/proxyd/proxyd/integration_tests/testdata/caching.toml b/proxyd/integration_tests/testdata/caching.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/caching.toml rename to proxyd/integration_tests/testdata/caching.toml diff --git a/proxyd/proxyd/integration_tests/testdata/consensus.toml b/proxyd/integration_tests/testdata/consensus.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/consensus.toml rename to proxyd/integration_tests/testdata/consensus.toml diff --git a/proxyd/proxyd/integration_tests/testdata/consensus_responses.yml b/proxyd/integration_tests/testdata/consensus_responses.yml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/consensus_responses.yml rename to proxyd/integration_tests/testdata/consensus_responses.yml diff --git a/proxyd/proxyd/integration_tests/testdata/failover.toml b/proxyd/integration_tests/testdata/failover.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/failover.toml rename to proxyd/integration_tests/testdata/failover.toml diff --git a/proxyd/proxyd/integration_tests/testdata/fallback.toml b/proxyd/integration_tests/testdata/fallback.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/fallback.toml rename to proxyd/integration_tests/testdata/fallback.toml diff --git a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml b/proxyd/integration_tests/testdata/frontend_rate_limit.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml rename to proxyd/integration_tests/testdata/frontend_rate_limit.toml diff --git a/proxyd/proxyd/integration_tests/testdata/max_rpc_conns.toml b/proxyd/integration_tests/testdata/max_rpc_conns.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/max_rpc_conns.toml rename to proxyd/integration_tests/testdata/max_rpc_conns.toml diff --git a/proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml b/proxyd/integration_tests/testdata/out_of_service_interval.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/out_of_service_interval.toml rename to proxyd/integration_tests/testdata/out_of_service_interval.toml diff --git a/proxyd/proxyd/integration_tests/testdata/retries.toml b/proxyd/integration_tests/testdata/retries.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/retries.toml rename to proxyd/integration_tests/testdata/retries.toml diff --git a/proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml b/proxyd/integration_tests/testdata/sender_rate_limit.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/sender_rate_limit.toml rename to proxyd/integration_tests/testdata/sender_rate_limit.toml diff --git a/proxyd/proxyd/integration_tests/testdata/size_limits.toml b/proxyd/integration_tests/testdata/size_limits.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/size_limits.toml rename to proxyd/integration_tests/testdata/size_limits.toml diff --git a/proxyd/proxyd/integration_tests/testdata/smoke.toml b/proxyd/integration_tests/testdata/smoke.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/smoke.toml rename to proxyd/integration_tests/testdata/smoke.toml diff --git a/proxyd/proxyd/integration_tests/testdata/testdata.txt b/proxyd/integration_tests/testdata/testdata.txt similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/testdata.txt rename to proxyd/integration_tests/testdata/testdata.txt diff --git a/proxyd/proxyd/integration_tests/testdata/whitelist.toml b/proxyd/integration_tests/testdata/whitelist.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/whitelist.toml rename to proxyd/integration_tests/testdata/whitelist.toml diff --git a/proxyd/proxyd/integration_tests/testdata/ws.toml b/proxyd/integration_tests/testdata/ws.toml similarity index 100% rename from proxyd/proxyd/integration_tests/testdata/ws.toml rename to proxyd/integration_tests/testdata/ws.toml diff --git a/proxyd/proxyd/integration_tests/util_test.go b/proxyd/integration_tests/util_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/util_test.go rename to proxyd/integration_tests/util_test.go diff --git a/proxyd/proxyd/integration_tests/validation_test.go b/proxyd/integration_tests/validation_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/validation_test.go rename to proxyd/integration_tests/validation_test.go diff --git a/proxyd/proxyd/integration_tests/ws_test.go b/proxyd/integration_tests/ws_test.go similarity index 100% rename from proxyd/proxyd/integration_tests/ws_test.go rename to proxyd/integration_tests/ws_test.go diff --git a/proxyd/proxyd/methods.go b/proxyd/methods.go similarity index 100% rename from proxyd/proxyd/methods.go rename to proxyd/methods.go diff --git a/proxyd/proxyd/metrics.go b/proxyd/metrics.go similarity index 100% rename from proxyd/proxyd/metrics.go rename to proxyd/metrics.go diff --git a/proxyd/proxyd/pkg/avg-sliding-window/sliding.go b/proxyd/pkg/avg-sliding-window/sliding.go similarity index 100% rename from proxyd/proxyd/pkg/avg-sliding-window/sliding.go rename to proxyd/pkg/avg-sliding-window/sliding.go diff --git a/proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go b/proxyd/pkg/avg-sliding-window/sliding_test.go similarity index 100% rename from proxyd/proxyd/pkg/avg-sliding-window/sliding_test.go rename to proxyd/pkg/avg-sliding-window/sliding_test.go diff --git a/proxyd/proxyd/proxyd.go b/proxyd/proxyd.go similarity index 100% rename from proxyd/proxyd/proxyd.go rename to proxyd/proxyd.go diff --git a/proxyd/proxyd/reader.go b/proxyd/reader.go similarity index 100% rename from proxyd/proxyd/reader.go rename to proxyd/reader.go diff --git a/proxyd/proxyd/reader_test.go b/proxyd/reader_test.go similarity index 100% rename from proxyd/proxyd/reader_test.go rename to proxyd/reader_test.go diff --git a/proxyd/proxyd/redis.go b/proxyd/redis.go similarity index 100% rename from proxyd/proxyd/redis.go rename to proxyd/redis.go diff --git a/proxyd/proxyd/rewriter.go b/proxyd/rewriter.go similarity index 100% rename from proxyd/proxyd/rewriter.go rename to proxyd/rewriter.go diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/rewriter_test.go similarity index 100% rename from proxyd/proxyd/rewriter_test.go rename to proxyd/rewriter_test.go diff --git a/proxyd/proxyd/rpc.go b/proxyd/rpc.go similarity index 100% rename from proxyd/proxyd/rpc.go rename to proxyd/rpc.go diff --git a/proxyd/proxyd/rpc_test.go b/proxyd/rpc_test.go similarity index 100% rename from proxyd/proxyd/rpc_test.go rename to proxyd/rpc_test.go diff --git a/proxyd/proxyd/server.go b/proxyd/server.go similarity index 100% rename from proxyd/proxyd/server.go rename to proxyd/server.go diff --git a/proxyd/proxyd/string_set.go b/proxyd/string_set.go similarity index 100% rename from proxyd/proxyd/string_set.go rename to proxyd/string_set.go diff --git a/proxyd/proxyd/tls.go b/proxyd/tls.go similarity index 100% rename from proxyd/proxyd/tls.go rename to proxyd/tls.go diff --git a/proxyd/proxyd/tools/mockserver/handler/handler.go b/proxyd/tools/mockserver/handler/handler.go similarity index 100% rename from proxyd/proxyd/tools/mockserver/handler/handler.go rename to proxyd/tools/mockserver/handler/handler.go diff --git a/proxyd/proxyd/tools/mockserver/main.go b/proxyd/tools/mockserver/main.go similarity index 100% rename from proxyd/proxyd/tools/mockserver/main.go rename to proxyd/tools/mockserver/main.go diff --git a/proxyd/proxyd/tools/mockserver/node1.yml b/proxyd/tools/mockserver/node1.yml similarity index 100% rename from proxyd/proxyd/tools/mockserver/node1.yml rename to proxyd/tools/mockserver/node1.yml diff --git a/proxyd/proxyd/tools/mockserver/node2.yml b/proxyd/tools/mockserver/node2.yml similarity index 100% rename from proxyd/proxyd/tools/mockserver/node2.yml rename to proxyd/tools/mockserver/node2.yml From 82c9581727c0af09a35ad094d16c91bea4bda318 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 14:34:44 -0500 Subject: [PATCH 201/212] feat: linting fixes --- proxyd/backend_test.go | 3 ++- proxyd/cmd/proxyd/main.go | 1 + proxyd/reader_test.go | 3 ++- proxyd/server.go | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/proxyd/backend_test.go b/proxyd/backend_test.go index 7be23bf..73ebebf 100644 --- a/proxyd/backend_test.go +++ b/proxyd/backend_test.go @@ -1,8 +1,9 @@ package proxyd import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestStripXFF(t *testing.T) { diff --git a/proxyd/cmd/proxyd/main.go b/proxyd/cmd/proxyd/main.go index 3daa80b..c2b613c 100644 --- a/proxyd/cmd/proxyd/main.go +++ b/proxyd/cmd/proxyd/main.go @@ -115,6 +115,7 @@ func StartPProf(hostname string, port int) *http.Server { Addr: addr, } + // nolint:errcheck go srv.ListenAndServe() return srv diff --git a/proxyd/reader_test.go b/proxyd/reader_test.go index 2ee2345..396b9bf 100644 --- a/proxyd/reader_test.go +++ b/proxyd/reader_test.go @@ -1,10 +1,11 @@ package proxyd import ( - "github.com/stretchr/testify/require" "io" "strings" "testing" + + "github.com/stretchr/testify/require" ) func TestLimitReader(t *testing.T) { diff --git a/proxyd/server.go b/proxyd/server.go index 527c2e6..c663f42 100644 --- a/proxyd/server.go +++ b/proxyd/server.go @@ -573,7 +573,7 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL } servedByString := "" - for sb, _ := range servedBy { + for sb := range servedBy { if servedByString != "" { servedByString += ", " } From 95b55ca398e4c585cf2a278d7022602296b23083 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 14:42:35 -0500 Subject: [PATCH 202/212] feat: add sudo cat for adding a nameserver instructions --- .circleci/continue_config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 1450834..d42e166 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -151,7 +151,7 @@ jobs: - run: sudo sed -i '13 i \ \ \ \ \ \ \ \ \ \ \ \ nameservers:' /etc/netplan/50-cloud-init.yaml - run: sudo sed -i '14 i \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ addresses:' /etc/netplan/50-cloud-init.yaml - run: sudo sed -i "s/addresses:/ addresses":" [8.8.8.8, 8.8.4.4] /g" /etc/netplan/50-cloud-init.yaml - - run: cat /etc/netplan/50-cloud-init.yaml + - run: sudo cat /etc/netplan/50-cloud-init.yaml - run: sudo netplan apply - run: name: Publish From f05bee2a46c1b2b878486731df65406a53eb7329 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 16:08:34 -0500 Subject: [PATCH 203/212] feat: update the publish repository --- .circleci/continue_config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index d42e166..2d4105c 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -412,6 +412,6 @@ workflows: docker_name: proxyd docker_tags: <>,<> context: - - oplabs-gcr + - oplabs-gcr-release requires: - proxyd-docker-build From adec715b0eefed864a28f46088d82ed77928f083 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 16:57:32 -0500 Subject: [PATCH 204/212] feat: add filter to run relase only when a release tag is created --- .circleci/continue_config.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 2d4105c..89d79a7 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -408,6 +408,11 @@ workflows: docker_tags: <>,<> docker_context: . - docker-publish: + filters: + tags: + only: /^proxyd\/v.*/ + branches: + ignore: /.*/ name: proxyd-docker-publish docker_name: proxyd docker_tags: <>,<> From 7b607ea932ab4847c2bffef4ea477c5e62f5a6be Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 18:27:05 -0500 Subject: [PATCH 205/212] feat: v1 tag-service and release flow for proxyd --- .circleci/continue_config.yml | 51 +++++++++++- .github/workflows/tag-service.yml | 55 +++++++++++++ ops/tag-service/.gitignore | 1 + ops/tag-service/README.md | 21 +++++ ops/tag-service/requirements.txt | 2 + ops/tag-service/tag-service.py | 124 ++++++++++++++++++++++++++++++ ops/tag-service/tag-tool.py | 93 ++++++++++++++++++++++ 7 files changed, 343 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/tag-service.yml create mode 100644 ops/tag-service/.gitignore create mode 100644 ops/tag-service/README.md create mode 100644 ops/tag-service/requirements.txt create mode 100755 ops/tag-service/tag-service.py create mode 100644 ops/tag-service/tag-tool.py diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 89d79a7..0f56986 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -407,16 +407,59 @@ workflows: docker_name: proxyd docker_tags: <>,<> docker_context: . - - docker-publish: + # - docker-publish: + # filters: + # tags: + # only: /^proxyd\/v.*/ + # branches: + # ignore: /.*/ + # name: proxyd-docker-publish + # docker_name: proxyd + # docker_tags: <>,<> + # context: + # - oplabs-gcr-release + # requires: + # - proxyd-docker-build + + release: + when: + not: + equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] + jobs: + - hold: + type: approval + filters: + tags: + only: /^(proxyd|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)\/v.*/ + branches: + ignore: /.*/ + - docker-build: + name: op-ufm-docker-release + filters: + tags: + only: /^op-ufm\/v.*/ + branches: + ignore: /.*/ + docker_name: op-ufm + docker_tags: <> + publish: true + release: true + context: + - oplabs-gcr-release + requires: + - hold + - docker-build: + name: proxyd-docker-release filters: tags: only: /^proxyd\/v.*/ branches: ignore: /.*/ - name: proxyd-docker-publish docker_name: proxyd - docker_tags: <>,<> + docker_tags: <> + publish: true + release: true context: - oplabs-gcr-release requires: - - proxyd-docker-build + - hold diff --git a/.github/workflows/tag-service.yml b/.github/workflows/tag-service.yml new file mode 100644 index 0000000..60baa6f --- /dev/null +++ b/.github/workflows/tag-service.yml @@ -0,0 +1,55 @@ +name: Tag Service + +on: + workflow_dispatch: + inputs: + bump: + description: 'How much to bump the version by' + required: true + type: choice + options: + - major + - minor + - patch + - prerelease + - finalize-prerelease + service: + description: 'Which service to release' + required: true + type: choice + options: + - op-ufm + - proxyd + prerelease: + description: Increment major/minor/patch as prerelease? + required: false + type: boolean + default: false + +jobs: + release: + runs-on: ubuntu-latest + environment: op-stack-production + steps: + - uses: actions/checkout@v4 + - name: Fetch tags + run: git fetch --tags origin --force + - name: Setup Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install deps + run: pip install -r requirements.txt + working-directory: ops/tag-service + - run: ops/tag-service/tag-service.py --bump="$BUMP" --service="$SERVICE" + env: + INPUT_GITHUB_TOKEN: ${{ github.token }} + BUMP: ${{ github.event.inputs.bump }} + SERVICE: ${{ github.event.inputs.service }} + if: ${{ github.event.inputs.prerelease == 'false' }} + - run: ops/tag-service/tag-service.py --bump="$BUMP" --service="$SERVICE" --pre-release + env: + INPUT_GITHUB_TOKEN: ${{ github.token }} + BUMP: ${{ github.event.inputs.bump }} + SERVICE: ${{ github.event.inputs.service }} + if: ${{ github.event.inputs.prerelease == 'true' }} diff --git a/ops/tag-service/.gitignore b/ops/tag-service/.gitignore new file mode 100644 index 0000000..f5e96db --- /dev/null +++ b/ops/tag-service/.gitignore @@ -0,0 +1 @@ +venv \ No newline at end of file diff --git a/ops/tag-service/README.md b/ops/tag-service/README.md new file mode 100644 index 0000000..b1fdc89 --- /dev/null +++ b/ops/tag-service/README.md @@ -0,0 +1,21 @@ +# Tag Service +Tag Service is a Github action which builds new tags and applies them to services in the monorepo. +It accepts: +* Service name +* Bump Amount [major, minor, patch] +* Prerelease and Finalize-Prerelease (to add/remove `rc` versions) + +It can be triggered from the Github Actions panel in the monorepo + +# Tag Tool +Tag Tool is a minimal rewrite of the Tag Service to let operators prepare and commit tags from commandline +It accepts: +* Service name +* Bump Amount [major, minor, patch, prerelease, finalize-prerelease] + +Tag Tool is meant to be run locally, and *does not* perform any write operations. Instead, it prints the git commands to console for the operator to use. + +Additionally, a special service name "op-stack" is available, which will bump versions for `op-node`, `op-batcher` and `op-proposer` from the highest semver amongst them. + +To run Tag Tool locally, the only dependency is `pip install semver` + diff --git a/ops/tag-service/requirements.txt b/ops/tag-service/requirements.txt new file mode 100644 index 0000000..c01ed2c --- /dev/null +++ b/ops/tag-service/requirements.txt @@ -0,0 +1,2 @@ +click==8.1.3 +semver==3.0.0-dev4 diff --git a/ops/tag-service/tag-service.py b/ops/tag-service/tag-service.py new file mode 100755 index 0000000..93c9e63 --- /dev/null +++ b/ops/tag-service/tag-service.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +import logging.config +import os +import re +import subprocess +import sys + +import click +import semver + +# Minimum version numbers for packages migrating from legacy versioning. +MIN_VERSIONS = { + 'proxyd': '3.16.0', +} + +VALID_BUMPS = ('major', 'minor', 'patch', 'prerelease', 'finalize-prerelease') + +MESSAGE_TEMPLATE = '[tag-service-release] Tag {service} at {version}' + +LOGGING_CONFIG = { + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'standard': { + 'format': '%(asctime)s [%(levelname)s]: %(message)s' + }, + }, + 'handlers': { + 'default': { + 'level': 'INFO', + 'formatter': 'standard', + 'class': 'logging.StreamHandler', + 'stream': 'ext://sys.stderr' + }, + }, + 'loggers': { + '': { + 'handlers': ['default'], + 'level': 'INFO', + 'propagate': False + }, + } +} + +logging.config.dictConfig(LOGGING_CONFIG) +log = logging.getLogger(__name__) + + +@click.command() +@click.option('--bump', required=True, type=click.Choice(VALID_BUMPS)) +@click.option('--service', required=True, type=click.Choice(list(MIN_VERSIONS.keys()))) +@click.option('--pre-release/--no-pre-release', default=False) +def tag_version(bump, service, pre_release): + tags = subprocess.run(['git', 'tag', '--list'], capture_output=True, check=True) \ + .stdout.decode('utf-8').splitlines() + + # Filter out tags that don't match the service name, and tags + # for prerelease versions. + version_pattern = f'^{service}/v\\d+\\.\\d+\\.\\d+(-rc\\.\\d+)?$' + svc_versions = [t.replace(f'{service}/v', '') for t in tags if re.match(version_pattern, t)] + svc_versions = sorted(svc_versions, key=lambda v: semver.Version.parse(v), reverse=True) + + if pre_release and bump == 'prerelease': + raise Exception('Cannot use --bump=prerelease with --pre-release') + + if pre_release and bump == 'finalize-prerelease': + raise Exception('Cannot use --bump=finalize-prerelease with --pre-release') + + if len(svc_versions) == 0: + latest_version = MIN_VERSIONS[service] + else: + latest_version = svc_versions[0] + + latest_version = semver.Version.parse(latest_version) + + log.info(f'Latest version: v{latest_version}') + + if bump == 'major': + bumped = latest_version.bump_major() + elif bump == 'minor': + bumped = latest_version.bump_minor() + elif bump == 'patch': + bumped = latest_version.bump_patch() + elif bump == 'prerelease': + bumped = latest_version.bump_prerelease() + elif bump == 'finalize-prerelease': + bumped = latest_version.finalize_version() + else: + raise Exception('Invalid bump type: {}'.format(bump)) + + if pre_release: + bumped = bumped.bump_prerelease() + + new_version = 'v' + str(bumped) + new_tag = f'{service}/{new_version}' + + log.info(f'Bumped version: {new_version}') + + log.info('Configuring git') + # The below env vars are set by GHA. + gh_actor = os.environ['GITHUB_ACTOR'] + gh_token = os.environ['INPUT_GITHUB_TOKEN'] + gh_repo = os.environ['GITHUB_REPOSITORY'] + origin_url = f'https://{gh_actor}:${gh_token}@github.com/{gh_repo}.git' + subprocess.run(['git', 'config', 'user.name', gh_actor], check=True) + subprocess.run(['git', 'config', 'user.email', f'{gh_actor}@users.noreply.github.com'], check=True) + subprocess.run(['git', 'remote', 'set-url', 'origin', origin_url], check=True) + + log.info(f'Creating tag: {new_tag}') + subprocess.run([ + 'git', + 'tag', + '-a', + new_tag, + '-m', + MESSAGE_TEMPLATE.format(service=service, version=new_version) + ], check=True) + + log.info('Pushing tag to origin') + subprocess.run(['git', 'push', 'origin', new_tag], check=True) + + +if __name__ == '__main__': + tag_version() diff --git a/ops/tag-service/tag-tool.py b/ops/tag-service/tag-tool.py new file mode 100644 index 0000000..f3fd847 --- /dev/null +++ b/ops/tag-service/tag-tool.py @@ -0,0 +1,93 @@ +import argparse +import subprocess +import re +import semver + +SERVICES = [ + 'ci-builder', + 'ci-builder-rust', + 'chain-mon', + 'op-node', + 'op-batcher', + 'op-challenger', + 'op-dispute-mon', + 'op-proposer', + 'da-server', + 'proxyd', + 'op-heartbeat', + 'op-contracts', + 'test', + 'op-stack', # special case for tagging op-node, op-batcher, and op-proposer together + 'op-conductor', +] +VERSION_PATTERN = '^{service}/v\\d+\\.\\d+\\.\\d+(-rc\\.\\d+)?$' +GIT_TAG_COMMAND = 'git tag -a {tag} -m "{message}"' +GIT_PUSH_COMMAND = 'git push origin {tag}' + +def new_tag(service, version, bump): + if bump == 'major': + bumped = version.bump_major() + elif bump == 'minor': + bumped = version.bump_minor() + elif bump == 'patch': + bumped = version.bump_patch() + elif bump == 'prerelease': + bumped = version.bump_prerelease() + elif bump == 'finalize-prerelease': + bumped = version.finalize_version() + else: + raise Exception('Invalid bump type: {}'.format(bump)) + return f'{service}/v{bumped}' + +def latest_version(service): + # Get the list of tags from the git repository. + tags = subprocess.run(['git', 'tag', '--list', f'{service}/v*'], capture_output=True, check=True) \ + .stdout.decode('utf-8').splitlines() + # Filter out tags that don't match the service name, and tags for prerelease versions. + svc_versions = sorted([t.replace(f'{service}/v', '') for t in tags]) + if len(svc_versions) == 0: + raise Exception(f'No tags found for service: {service}') + return svc_versions[-1] + +def latest_among_services(services): + latest = '0.0.0' + for service in services: + candidate = latest_version(service) + if semver.compare(candidate, latest) > 0: + latest = candidate + return latest + +def main(): + parser = argparse.ArgumentParser(description='Create a new git tag for a service') + parser.add_argument('--service', type=str, help='The name of the Service') + parser.add_argument('--bump', type=str, help='The type of bump to apply to the version number') + parser.add_argument('--message', type=str, help='Message to include in git tag', default='[tag-tool-release]') + args = parser.parse_args() + + service = args.service + + if service == 'op-stack': + latest = latest_among_services(['op-node', 'op-batcher', 'op-proposer']) + else: + latest = latest_version(service) + + bumped = new_tag(service, semver.VersionInfo.parse(latest), args.bump) + + print(f'latest tag: {latest}') + print(f'new tag: {bumped}') + print('run the following commands to create the new tag:\n') + # special case for tagging op-node, op-batcher, and op-proposer together. All three would share the same semver + if args.service == 'op-stack': + print(GIT_TAG_COMMAND.format(tag=bumped.replace('op-stack', 'op-node'), message=args.message)) + print(GIT_PUSH_COMMAND.format(tag=bumped.replace('op-stack', 'op-node'))) + print(GIT_TAG_COMMAND.format(tag=bumped.replace('op-stack', 'op-batcher'), message=args.message)) + print(GIT_PUSH_COMMAND.format(tag=bumped.replace('op-stack', 'op-batcher'))) + print(GIT_TAG_COMMAND.format(tag=bumped.replace('op-stack', 'op-proposer'), message=args.message)) + print(GIT_PUSH_COMMAND.format(tag=bumped.replace('op-stack', 'op-proposer'))) + else: + print(GIT_TAG_COMMAND.format(tag=bumped, message=args.message)) + print(GIT_PUSH_COMMAND.format(tag=bumped)) + +if __name__ == "__main__": + main() + From 22f3130bbb55281b40df0603b3e6feea5095a727 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 18:37:00 -0500 Subject: [PATCH 206/212] feat: add dockerfile and docker context to release workflow --- .circleci/continue_config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 0f56986..b0e3895 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -442,6 +442,8 @@ workflows: ignore: /.*/ docker_name: op-ufm docker_tags: <> + docker_context: . + docker_file: op-ufm/Dockerfile publish: true release: true context: @@ -457,6 +459,8 @@ workflows: ignore: /.*/ docker_name: proxyd docker_tags: <> + docker_context: . + docker_file: proxyd/Dockerfile publish: true release: true context: From 80af9610eb4c28c7a856a7a96e7806ef3abdeea4 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 18:44:07 -0500 Subject: [PATCH 207/212] feat: adding release flow for proxyd and op-ufm --- .circleci/continue_config.yml | 47 ++++++++++++++++------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index b0e3895..7cfbad1 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -383,14 +383,6 @@ workflows: 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 op-proxyd: when: or: [<< pipeline.parameters.run-build-proxyd >>, << pipeline.parameters.run-all >>] @@ -407,20 +399,6 @@ workflows: docker_name: proxyd docker_tags: <>,<> docker_context: . - # - docker-publish: - # filters: - # tags: - # only: /^proxyd\/v.*/ - # branches: - # ignore: /.*/ - # name: proxyd-docker-publish - # docker_name: proxyd - # docker_tags: <>,<> - # context: - # - oplabs-gcr-release - # requires: - # - proxyd-docker-build - release: when: not: @@ -444,12 +422,18 @@ workflows: docker_tags: <> docker_context: . docker_file: op-ufm/Dockerfile - publish: true - release: true context: - oplabs-gcr-release requires: - hold + - docker-publish: + name: op-ufm-docker-publish + docker_name: op-ufm + docker_tags: <> + context: + - oplabs-gcr + requires: + - op-ufm-docker-build - docker-build: name: proxyd-docker-release filters: @@ -461,9 +445,20 @@ workflows: docker_tags: <> docker_context: . docker_file: proxyd/Dockerfile - publish: true - release: true context: - oplabs-gcr-release requires: - hold + - docker-publish: + name: proxyd-docker-release + filters: + tags: + only: /^proxyd\/v.*/ + branches: + ignore: /.*/ + docker_name: proxyd + docker_tags: <> + context: + - oplabs-gcr-release + requires: + - proxyd-docker-build From 3975028e4ee214d4d780b3060cac2a3cae3d2059 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 18:48:11 -0500 Subject: [PATCH 208/212] feat: correct the names of the pre-req steps --- .circleci/continue_config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 7cfbad1..74a524e 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -412,7 +412,7 @@ workflows: branches: ignore: /.*/ - docker-build: - name: op-ufm-docker-release + name: op-ufm-docker-build filters: tags: only: /^op-ufm\/v.*/ @@ -435,7 +435,7 @@ workflows: requires: - op-ufm-docker-build - docker-build: - name: proxyd-docker-release + name: proxyd-docker-build filters: tags: only: /^proxyd\/v.*/ From 61446911d63f690b8fa95ae78f7b60d16e482dd1 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 18:56:21 -0500 Subject: [PATCH 209/212] feat: temp run release flow --- .circleci/continue_config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 74a524e..9d7e393 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -401,8 +401,9 @@ workflows: docker_context: . release: when: - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] + or: [<< pipeline.parameters.run-build-proxyd >>, << pipeline.parameters.run-all >>] + # not: + # equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] jobs: - hold: type: approval From 39c794f81555498e65c1e091707de45c307fb5b8 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Tue, 11 Jun 2024 19:06:44 -0500 Subject: [PATCH 210/212] feat: adding back normal release conditions --- .circleci/continue_config.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 9d7e393..74a524e 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -401,9 +401,8 @@ workflows: docker_context: . release: when: - or: [<< pipeline.parameters.run-build-proxyd >>, << pipeline.parameters.run-all >>] - # not: - # equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] + not: + equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] jobs: - hold: type: approval From ca86c23cd34062b67bfa89032641c30bae2e3b2f Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Thu, 13 Jun 2024 10:04:23 -0400 Subject: [PATCH 211/212] feat: bump min version to 4.6.1 --- ops/tag-service/tag-service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/tag-service/tag-service.py b/ops/tag-service/tag-service.py index 93c9e63..c911043 100755 --- a/ops/tag-service/tag-service.py +++ b/ops/tag-service/tag-service.py @@ -10,7 +10,7 @@ import semver # Minimum version numbers for packages migrating from legacy versioning. MIN_VERSIONS = { - 'proxyd': '3.16.0', + 'proxyd': '4.6.1', } VALID_BUMPS = ('major', 'minor', 'patch', 'prerelease', 'finalize-prerelease') From 05b982aa281b0aab1a2b7c771874e4da6b9a2917 Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Thu, 13 Jun 2024 11:56:29 -0400 Subject: [PATCH 212/212] feat: updated the services selector to container the services with in infra --- ops/tag-service/tag-tool.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/ops/tag-service/tag-tool.py b/ops/tag-service/tag-tool.py index f3fd847..19fbf7d 100644 --- a/ops/tag-service/tag-tool.py +++ b/ops/tag-service/tag-tool.py @@ -4,21 +4,9 @@ import re import semver SERVICES = [ - 'ci-builder', - 'ci-builder-rust', - 'chain-mon', - 'op-node', - 'op-batcher', - 'op-challenger', - 'op-dispute-mon', - 'op-proposer', - 'da-server', 'proxyd', - 'op-heartbeat', - 'op-contracts', - 'test', - 'op-stack', # special case for tagging op-node, op-batcher, and op-proposer together - 'op-conductor', + 'op-ufm', + 'op-conductor-mon', ] VERSION_PATTERN = '^{service}/v\\d+\\.\\d+\\.\\d+(-rc\\.\\d+)?$' GIT_TAG_COMMAND = 'git tag -a {tag} -m "{message}"'