diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2f1f47b..d41a165 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,4 +24,3 @@ jobs: with: version: v1.60 args: --timeout 3m - diff --git a/README.md b/README.md index d0ac641..1da7947 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,8 @@ An optional storage caching CLI flag `--routing.cache-targets` can be leveraged To the see list of available metrics, run `./bin/eigenda-proxy doc metrics` +To quickly set up monitoring dashboard, add eigenda-proxy metrics endpoint to a reachable prometheus server config as a scrape target, add prometheus datasource to Grafana to, and import the existing [Grafana dashboard JSON file](./grafana_dashboard.json) + ## Deployment Guide ### Hardware Requirements diff --git a/commitments/mode.go b/commitments/mode.go index d1c1139..e906828 100644 --- a/commitments/mode.go +++ b/commitments/mode.go @@ -5,6 +5,12 @@ import ( "fmt" ) +type CommitmentMeta struct { + Mode CommitmentMode + // CertVersion is shared for all modes and denotes version of the EigenDA certificate + CertVersion byte +} + type CommitmentMode string const ( diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..9fd6265 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,57 @@ +services: + eigenda_proxy: + build: + context: . + dockerfile: Dockerfile + container_name: eigenda-proxy + environment: + - EIGENDA_PROXY_ADDR=0.0.0.0 + - EIGENDA_PROXY_PORT=4242 + - MEMSTORE_ENABLED=false + - MEMSTORE_EXPIRATION=45m + - EIGENDA_PROXY_SIGNER_PRIVATE_KEY_HEX=$PRIVATE_KEY + - EIGENDA_PROXY_EIGENDA_DISPERSER_RPC=disperser-holesky.eigenda.xyz:443 + - EIGENDA_PROXY_SERVICE_MANAGER_ADDR=0xD4A7E1Bd8015057293f0D0A557088c286942e84b + - EIGENDA_PROXY_ETH_RPC=$ETH_RPC + - EIGENDA_PROXY_ETH_CONFIRMATION_DEPTH=0 + - EIGENDA_PROXY_METRICS_ADDR=0.0.0.0 + - EIGENDA_PROXY_METRICS_ENABLED=true + - EIGENDA_PROXY_METRICS_PORT=7300 + ports: + - 4242:4242 + - 7300:7300 + + prometheus: + image: prom/prometheus:latest + container_name: prometheus + volumes: + - ./monitor/prometheus.yml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + command: + - "--config.file=/etc/prometheus/prometheus.yml" + + grafana: + image: grafana/grafana:latest + container_name: grafana + ports: + - "127.0.0.1:3000:3000" + volumes: + - ./monitor/grafana/provisioning/:/etc/grafana/provisioning/:ro + - ./monitor/grafana/dashboards:/var/lib/grafana/dashboards + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + depends_on: + - prometheus + + traffic-generator: + image: alpine:latest + build: scripts/ + container_name: traffic_generator + depends_on: + - eigenda_proxy + volumes: + - ./scripts/:/scripts/ + +volumes: + grafana-data: diff --git a/metrics/metrics.go b/metrics/metrics.go index d46dec7..a7ce1ba 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -29,7 +29,7 @@ type Config struct { type Metricer interface { RecordInfo(version string) RecordUp() - RecordRPCServerRequest(method string) func(status string) + RecordRPCServerRequest(method string) func(status string, commitmentMode string, version string) Document() []metrics.DocumentedMetric } @@ -40,6 +40,7 @@ type Metrics struct { Up prometheus.Gauge HTTPServerRequestsTotal *prometheus.CounterVec + HTTPServerBadRequestHeader *prometheus.CounterVec HTTPServerRequestDurationSeconds *prometheus.HistogramVec registry *prometheus.Registry @@ -79,7 +80,15 @@ func NewMetrics(subsystem string) *Metrics { Name: "requests_total", Help: "Total requests to the HTTP server", }, []string{ - "method", "status", + "method", "status", "commitment_mode", "DA_cert_version", + }), + HTTPServerBadRequestHeader: factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: httpServerSubsystem, + Name: "requests_bad_header_total", + Help: "Total requests to the HTTP server with bad headers", + }, []string{ + "method", "error_type", }), HTTPServerRequestDurationSeconds: factory.NewHistogramVec(prometheus.HistogramOpts{ Namespace: namespace, @@ -90,7 +99,7 @@ func NewMetrics(subsystem string) *Metrics { Buckets: prometheus.ExponentialBucketsRange(0.05, 1200, 20), Help: "Histogram of HTTP server request durations", }, []string{ - "method", // no status on histograms because those are very expensive + "method", "commitment_mode", "DA_cert_version", // no status on histograms because those are very expensive }), registry: registry, factory: factory, @@ -112,12 +121,12 @@ func (m *Metrics) RecordUp() { // RecordRPCServerRequest is a helper method to record an incoming HTTP request. // It bumps the requests metric, and tracks how long it takes to serve a response, // including the HTTP status code. -func (m *Metrics) RecordRPCServerRequest(method string) func(status string) { +func (m *Metrics) RecordRPCServerRequest(method string) func(status string, mode string, ver string) { // we don't want to track the status code on the histogram because that would // create a huge number of labels, and cost a lot on cloud hosted services timer := prometheus.NewTimer(m.HTTPServerRequestDurationSeconds.WithLabelValues(method)) - return func(status string) { - m.HTTPServerRequestsTotal.WithLabelValues(method, status).Inc() + return func(status, mode, ver string) { + m.HTTPServerRequestsTotal.WithLabelValues(method, status, mode, ver).Inc() timer.ObserveDuration() } } @@ -150,6 +159,6 @@ func (n *noopMetricer) RecordInfo(_ string) { func (n *noopMetricer) RecordUp() { } -func (n *noopMetricer) RecordRPCServerRequest(string) func(status string) { - return func(string) {} +func (n *noopMetricer) RecordRPCServerRequest(string) func(status, mode, ver string) { + return func(string, string, string) {} } diff --git a/monitor/grafana/dashboards/simple_dashboard.json b/monitor/grafana/dashboards/simple_dashboard.json new file mode 100644 index 0000000..eb7f48f --- /dev/null +++ b/monitor/grafana/dashboards/simple_dashboard.json @@ -0,0 +1,243 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "editorMode": "code", + "expr": "eigenda_proxy_default_rpc_server_requests_total{method=\"/put/\"}", + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "A" + } + ], + "title": "/put requests total", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 4, + "options": { + "displayMode": "gradient", + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "editorMode": "code", + "expr": "eigenda_proxy_default_rpc_server_request_duration_seconds_bucket{method=\"/put/\"}", + "format": "heatmap", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "/put requests duration", + "type": "bargauge" + }, + { + "datasource": { + "type": "loki", + "uid": "loki-datasource" + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 2, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki-datasource" + }, + "editorMode": "builder", + "expr": "{container=\"ops-bedrock-da-server-1\"} |= ``", + "queryType": "range", + "refId": "A" + } + ], + "title": "logs", + "type": "logs" + } + ], + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "EigenDA Proxy", + "uid": "ddw5n232n5vy8e", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/monitor/grafana/provisioning/dashboards/all.yml b/monitor/grafana/provisioning/dashboards/all.yml new file mode 100644 index 0000000..36fd178 --- /dev/null +++ b/monitor/grafana/provisioning/dashboards/all.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: true + editable: true + options: + path: /var/lib/grafana/dashboards \ No newline at end of file diff --git a/monitor/grafana/provisioning/datasources/all.yml b/monitor/grafana/provisioning/datasources/all.yml new file mode 100644 index 0000000..af7179f --- /dev/null +++ b/monitor/grafana/provisioning/datasources/all.yml @@ -0,0 +1,15 @@ +apiVersion: 1 + +deleteDatasources: +- name: 'Prometheus' + +datasources: +- access: 'proxy' + editable: true + is_default: true + name: 'Prometheus' + uid: 'ddshms3dlineoe' + org_id: 1 + type: 'prometheus' + url: 'http://prometheus:9090' + version: 1 \ No newline at end of file diff --git a/monitor/prometheus.yml b/monitor/prometheus.yml new file mode 100644 index 0000000..e508576 --- /dev/null +++ b/monitor/prometheus.yml @@ -0,0 +1,11 @@ +# my global config +global: + scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. + evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. + # scrape_timeout is set to the global default (10s). + +scrape_configs: + - job_name: "eigenda-proxy" + static_configs: + # configure this to point to the target eigenda-proxy instance's metrics port + - targets: ["localhost:7300"] diff --git a/server/config.go b/server/config.go index a90b3a1..a2aa398 100644 --- a/server/config.go +++ b/server/config.go @@ -138,9 +138,9 @@ func (cfg *Config) VerificationCfg() *verify.Config { G1Path: cfg.G1Path, G2PowerOf2Path: cfg.G2PowerOfTauPath, CacheDir: cfg.CacheDir, - SRSOrder: 268435456, // 2 ^ 32 - SRSNumberToLoad: numBytes / 32, // # of fp.Elements - NumWorker: uint64(runtime.GOMAXPROCS(0)), + SRSOrder: 268435456, // 2 ^ 32 + SRSNumberToLoad: numBytes / 32, // # of fr.Elements + NumWorker: uint64(runtime.GOMAXPROCS(0)), // #nosec G115 } if cfg.EthRPC == "" || cfg.SvcManagerAddr == "" { @@ -155,7 +155,7 @@ func (cfg *Config) VerificationCfg() *verify.Config { RPCURL: cfg.EthRPC, SvcManagerAddr: cfg.SvcManagerAddr, KzgConfig: kzgCfg, - EthConfirmationDepth: uint64(cfg.EthConfirmationDepth), + EthConfirmationDepth: uint64(cfg.EthConfirmationDepth), // #nosec G115 } } diff --git a/server/load_store.go b/server/load_store.go index e73f09d..47da09a 100644 --- a/server/load_store.go +++ b/server/load_store.go @@ -106,7 +106,7 @@ func LoadStoreRouter(ctx context.Context, cfg CLIConfig, log log.Logger) (store. log, &store.EigenDAStoreConfig{ MaxBlobSizeBytes: maxBlobLength, - EthConfirmationDepth: uint64(cfg.EigenDAConfig.EthConfirmationDepth), + EthConfirmationDepth: uint64(cfg.EigenDAConfig.EthConfirmationDepth), // #nosec G115 StatusQueryTimeout: cfg.EigenDAConfig.ClientConfig.StatusQueryTimeout, }, ) diff --git a/server/server.go b/server/server.go index 5c41c98..f6b1f05 100644 --- a/server/server.go +++ b/server/server.go @@ -30,6 +30,7 @@ const ( GetRoute = "/get/" PutRoute = "/put/" + Put = "put" DomainFilterKey = "domain" CommitmentModeKey = "commitment_mode" @@ -63,21 +64,14 @@ func NewServer(host string, port int, router store.IRouter, log log.Logger, } // WithMetrics is a middleware that records metrics for the route path. -func WithMetrics(handleFn func(http.ResponseWriter, *http.Request) error, +func WithMetrics(handleFn func(http.ResponseWriter, *http.Request) (commitments.CommitmentMeta, error), m metrics.Metricer) func(http.ResponseWriter, *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error { - // we use a commitment schema (https://github.com/Layr-Labs/eigenda-proxy?tab=readme-ov-file#commitment-schemas) - // where the first 3 bytes of the path are the commitment header - // commit type | da layer type | version byte - // we want to group all requests by commitment header, otherwise the prometheus metric labels will explode - // TODO: commitment header is different for non-op commitments. We will need to change this to accommodate other commitments. - // probably want (commitment mode, cert version) as the labels, since commit-type/da-layer are not relevant anyways. - commitmentHeader := r.URL.Path[:3] - recordDur := m.RecordRPCServerRequest(commitmentHeader) + recordDur := m.RecordRPCServerRequest(r.Method) - err := handleFn(w, r) + meta, err := handleFn(w, r) // we assume that every route will set the status header - recordDur(w.Header().Get("status")) + recordDur(w.Header().Get("status"), string(meta.Mode), string(meta.CertVersion)) return err } } @@ -156,78 +150,78 @@ func (svr *Server) Health(w http.ResponseWriter, _ *http.Request) error { return nil } -func (svr *Server) HandleGet(w http.ResponseWriter, r *http.Request) error { - ct, err := ReadCommitmentMode(r) +func (svr *Server) HandleGet(w http.ResponseWriter, r *http.Request) (commitments.CommitmentMeta, error) { + meta, err := ReadCommitmentMeta(r) if err != nil { svr.WriteBadRequest(w, invalidCommitmentMode) - return err + return meta, err } key := path.Base(r.URL.Path) - comm, err := commitments.StringToDecodedCommitment(key, ct) + comm, err := commitments.StringToDecodedCommitment(key, meta.Mode) if err != nil { svr.log.Info("failed to decode commitment", "err", err, "commitment", comm) w.WriteHeader(http.StatusBadRequest) - return err + return meta, err } - input, err := svr.router.Get(r.Context(), comm, ct) + input, err := svr.router.Get(r.Context(), comm, meta.Mode) if err != nil && errors.Is(err, ErrNotFound) { svr.WriteNotFound(w, err.Error()) - return err + return meta, err } if err != nil { svr.WriteInternalError(w, err) - return err + return meta, err } svr.WriteResponse(w, input) - return nil + return meta, nil } -func (svr *Server) HandlePut(w http.ResponseWriter, r *http.Request) error { - ct, err := ReadCommitmentMode(r) +func (svr *Server) HandlePut(w http.ResponseWriter, r *http.Request) (commitments.CommitmentMeta, error) { + meta, err := ReadCommitmentMeta(r) if err != nil { svr.WriteBadRequest(w, invalidCommitmentMode) - return err + return meta, err } input, err := io.ReadAll(r.Body) if err != nil { svr.log.Error("Failed to read request body", "err", err) w.WriteHeader(http.StatusBadRequest) - return err + return meta, err } key := path.Base(r.URL.Path) var comm []byte - if len(key) > 0 && key != "put" { // commitment key already provided (keccak256) - comm, err = commitments.StringToDecodedCommitment(key, ct) + if len(key) > 0 && key != Put { // commitment key already provided (keccak256) + comm, err = commitments.StringToDecodedCommitment(key, meta.Mode) if err != nil { svr.log.Info("failed to decode commitment", "err", err, "key", key) w.WriteHeader(http.StatusBadRequest) - return err + return meta, err } } - commitment, err := svr.router.Put(r.Context(), ct, comm, input) + commitment, err := svr.router.Put(r.Context(), meta.Mode, comm, input) if err != nil { svr.WriteInternalError(w, err) - return err + return meta, err } - responseCommit, err := commitments.EncodeCommitment(commitment, ct) + responseCommit, err := commitments.EncodeCommitment(commitment, meta.Mode) if err != nil { svr.log.Info("failed to encode commitment", "err", err) w.WriteHeader(http.StatusInternalServerError) - return err + return meta, err } svr.log.Info(fmt.Sprintf("write commitment: %x\n", comm)) // write out encoded commitment svr.WriteResponse(w, responseCommit) - return nil + return meta, nil } func (svr *Server) WriteResponse(w http.ResponseWriter, data []byte) { @@ -258,6 +252,24 @@ func (svr *Server) Port() int { return port } +// Read both commitment mode and version +func ReadCommitmentMeta(r *http.Request) (commitments.CommitmentMeta, error) { + // label requests with commitment mode and version + ct, err := ReadCommitmentMode(r) + if err != nil { + return commitments.CommitmentMeta{}, err + } + if ct == "" { + return commitments.CommitmentMeta{}, fmt.Errorf("commitment mode is empty") + } + cv, err := ReadCommitmentVersion(r, ct) + if err != nil { + // default to version 0 + return commitments.CommitmentMeta{Mode: ct, CertVersion: cv}, err + } + return commitments.CommitmentMeta{Mode: ct, CertVersion: cv}, nil +} + func ReadCommitmentMode(r *http.Request) (commitments.CommitmentMode, error) { query := r.URL.Query() key := query.Get(CommitmentModeKey) @@ -266,18 +278,18 @@ func ReadCommitmentMode(r *http.Request) (commitments.CommitmentMode, error) { } commit := path.Base(r.URL.Path) - if len(commit) > 0 && commit != "put" { // provided commitment in request params (op keccak256) + if len(commit) > 0 && commit != Put { // provided commitment in request params (op keccak256) if !strings.HasPrefix(commit, "0x") { commit = "0x" + commit } decodedCommit, err := hexutil.Decode(commit) if err != nil { - return commitments.SimpleCommitmentMode, err + return "", err } if len(decodedCommit) < 3 { - return commitments.SimpleCommitmentMode, fmt.Errorf("commitment is too short") + return "", fmt.Errorf("commitment is too short") } switch decodedCommit[0] { @@ -294,6 +306,31 @@ func ReadCommitmentMode(r *http.Request) (commitments.CommitmentMode, error) { return commitments.OptimismAltDA, nil } +func ReadCommitmentVersion(r *http.Request, mode commitments.CommitmentMode) (byte, error) { + commit := path.Base(r.URL.Path) + if len(commit) > 0 && commit != Put { // provided commitment in request params (op keccak256) + if !strings.HasPrefix(commit, "0x") { + commit = "0x" + commit + } + + decodedCommit, err := hexutil.Decode(commit) + if err != nil { + return 0, err + } + + if len(decodedCommit) < 3 { + return 0, fmt.Errorf("commitment is too short") + } + + if mode == commitments.OptimismAltDA || mode == commitments.SimpleCommitmentMode { + return decodedCommit[2], nil + } + + return decodedCommit[0], nil + } + return 0, nil +} + func (svr *Server) GetEigenDAStats() *store.Stats { return svr.router.GetEigenDAStore().Stats() } diff --git a/server/server_test.go b/server/server_test.go index cdc6ddf..9fd4462 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "testing" + "github.com/Layr-Labs/eigenda-proxy/commitments" "github.com/Layr-Labs/eigenda-proxy/metrics" "github.com/Layr-Labs/eigenda-proxy/mocks" "github.com/ethereum/go-ethereum/log" @@ -33,12 +34,13 @@ func TestGetHandler(t *testing.T) { server := NewServer("localhost", 8080, mockRouter, log.New(), metrics.NoopMetrics) tests := []struct { - name string - url string - mockBehavior func() - expectedCode int - expectedBody string - expectError bool + name string + url string + mockBehavior func() + expectedCode int + expectedBody string + expectError bool + expectedCommitmentMeta commitments.CommitmentMeta }{ { name: "Failure - Op Mode InvalidCommitmentKey", @@ -46,9 +48,10 @@ func TestGetHandler(t *testing.T) { mockBehavior: func() { // Error is triggered before calling the router }, - expectedCode: http.StatusBadRequest, - expectedBody: "", - expectError: true, + expectedCode: http.StatusBadRequest, + expectedBody: "", + expectError: true, + expectedCommitmentMeta: commitments.CommitmentMeta{}, }, { name: "Failure - Op Mode InvalidCommitmentKey", @@ -56,9 +59,10 @@ func TestGetHandler(t *testing.T) { mockBehavior: func() { // Error is triggered before calling the router }, - expectedCode: http.StatusBadRequest, - expectedBody: "", - expectError: true, + expectedCode: http.StatusBadRequest, + expectedBody: "", + expectError: true, + expectedCommitmentMeta: commitments.CommitmentMeta{}, }, { name: "Failure - Op Mode InvalidCommitmentKey", @@ -66,9 +70,10 @@ func TestGetHandler(t *testing.T) { mockBehavior: func() { // Error is triggered before calling the router }, - expectedCode: http.StatusBadRequest, - expectedBody: "", - expectError: true, + expectedCode: http.StatusBadRequest, + expectedBody: "", + expectError: true, + expectedCommitmentMeta: commitments.CommitmentMeta{}, }, { name: "Failure - OP Keccak256 Internal Server Error", @@ -76,9 +81,10 @@ func TestGetHandler(t *testing.T) { mockBehavior: func() { mockRouter.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("internal error")) }, - expectedCode: http.StatusInternalServerError, - expectedBody: "", - expectError: true, + expectedCode: http.StatusInternalServerError, + expectedBody: "", + expectError: true, + expectedCommitmentMeta: commitments.CommitmentMeta{Mode: commitments.OptimismGeneric, CertVersion: 0}, }, { name: "Success - OP Keccak256", @@ -86,9 +92,10 @@ func TestGetHandler(t *testing.T) { mockBehavior: func() { mockRouter.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil) }, - expectedCode: http.StatusOK, - expectedBody: testCommitStr, - expectError: false, + expectedCode: http.StatusOK, + expectedBody: testCommitStr, + expectError: false, + expectedCommitmentMeta: commitments.CommitmentMeta{Mode: commitments.OptimismGeneric, CertVersion: 0}, }, { name: "Failure - OP Alt-DA Internal Server Error", @@ -96,9 +103,10 @@ func TestGetHandler(t *testing.T) { mockBehavior: func() { mockRouter.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("internal error")) }, - expectedCode: http.StatusInternalServerError, - expectedBody: "", - expectError: true, + expectedCode: http.StatusInternalServerError, + expectedBody: "", + expectError: true, + expectedCommitmentMeta: commitments.CommitmentMeta{Mode: commitments.OptimismAltDA, CertVersion: 0}, }, { name: "Success - OP Alt-DA", @@ -106,9 +114,10 @@ func TestGetHandler(t *testing.T) { mockBehavior: func() { mockRouter.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil) }, - expectedCode: http.StatusOK, - expectedBody: testCommitStr, - expectError: false, + expectedCode: http.StatusOK, + expectedBody: testCommitStr, + expectError: false, + expectedCommitmentMeta: commitments.CommitmentMeta{Mode: commitments.OptimismAltDA, CertVersion: 0}, }, } @@ -119,13 +128,15 @@ func TestGetHandler(t *testing.T) { req := httptest.NewRequest(http.MethodGet, tt.url, nil) rec := httptest.NewRecorder() - err := server.HandleGet(rec, req) + meta, err := server.HandleGet(rec, req) if tt.expectError { require.Error(t, err) } else { require.NoError(t, err) } + require.Equal(t, tt.expectedCode, rec.Code) + require.Equal(t, tt.expectedCommitmentMeta, meta) require.Equal(t, tt.expectedBody, rec.Body.String()) }) } @@ -139,13 +150,14 @@ func TestPutHandler(t *testing.T) { server := NewServer("localhost", 8080, mockRouter, log.New(), metrics.NoopMetrics) tests := []struct { - name string - url string - body []byte - mockBehavior func() - expectedCode int - expectedBody string - expectError bool + name string + url string + body []byte + mockBehavior func() + expectedCode int + expectedBody string + expectError bool + expectedCommitmentMeta commitments.CommitmentMeta }{ { name: "Failure OP Keccak256 - TooShortCommitmentKey", @@ -154,9 +166,10 @@ func TestPutHandler(t *testing.T) { mockBehavior: func() { // Error is triggered before calling the router }, - expectedCode: http.StatusBadRequest, - expectedBody: "", - expectError: true, + expectedCode: http.StatusBadRequest, + expectedBody: "", + expectError: true, + expectedCommitmentMeta: commitments.CommitmentMeta{}, }, { name: "Failure OP Keccak256 - TooShortCommitmentKey", @@ -165,9 +178,10 @@ func TestPutHandler(t *testing.T) { mockBehavior: func() { // Error is triggered before calling the router }, - expectedCode: http.StatusBadRequest, - expectedBody: "", - expectError: true, + expectedCode: http.StatusBadRequest, + expectedBody: "", + expectError: true, + expectedCommitmentMeta: commitments.CommitmentMeta{}, }, { name: "Failure OP Keccak256 - InvalidCommitmentPrefixBytes", @@ -176,12 +190,13 @@ func TestPutHandler(t *testing.T) { mockBehavior: func() { // Error is triggered before calling the router }, - expectedCode: http.StatusBadRequest, - expectedBody: "", - expectError: true, + expectedCode: http.StatusBadRequest, + expectedBody: "", + expectError: true, + expectedCommitmentMeta: commitments.CommitmentMeta{}, }, { - name: "Failure OP Keccak256 - InternalServerError", + name: "Failure OP Mode Alt-DA - InternalServerError", url: "/put/", body: []byte("some data that will trigger an internal error"), mockBehavior: func() { @@ -190,6 +205,8 @@ func TestPutHandler(t *testing.T) { expectedCode: http.StatusInternalServerError, expectedBody: "", expectError: true, + // certification version is the third byte of the body, in this case it's "m" + expectedCommitmentMeta: commitments.CommitmentMeta{Mode: commitments.OptimismAltDA, CertVersion: 0}, }, { name: "Success OP Mode Alt-DA", @@ -198,9 +215,10 @@ func TestPutHandler(t *testing.T) { mockBehavior: func() { mockRouter.EXPECT().Put(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil) }, - expectedCode: http.StatusOK, - expectedBody: opGenericPrefixStr + testCommitStr, - expectError: false, + expectedCode: http.StatusOK, + expectedBody: opGenericPrefixStr + testCommitStr, + expectError: false, + expectedCommitmentMeta: commitments.CommitmentMeta{Mode: commitments.OptimismAltDA, CertVersion: 0}, }, { name: "Success OP Mode Keccak256", @@ -209,9 +227,10 @@ func TestPutHandler(t *testing.T) { mockBehavior: func() { mockRouter.EXPECT().Put(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil) }, - expectedCode: http.StatusOK, - expectedBody: opKeccakPrefix + testCommitStr, - expectError: false, + expectedCode: http.StatusOK, + expectedBody: opKeccakPrefix + testCommitStr, + expectError: false, + expectedCommitmentMeta: commitments.CommitmentMeta{Mode: commitments.OptimismGeneric, CertVersion: 0}, }, { name: "Success Simple Commitment Mode", @@ -220,9 +239,10 @@ func TestPutHandler(t *testing.T) { mockBehavior: func() { mockRouter.EXPECT().Put(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil) }, - expectedCode: http.StatusOK, - expectedBody: genericPrefix + testCommitStr, - expectError: false, + expectedCode: http.StatusOK, + expectedBody: genericPrefix + testCommitStr, + expectError: false, + expectedCommitmentMeta: commitments.CommitmentMeta{Mode: commitments.SimpleCommitmentMode, CertVersion: 0}, }, } @@ -233,7 +253,7 @@ func TestPutHandler(t *testing.T) { req := httptest.NewRequest(http.MethodPut, tt.url, bytes.NewReader(tt.body)) rec := httptest.NewRecorder() - err := server.HandlePut(rec, req) + meta, err := server.HandlePut(rec, req) if tt.expectError { require.Error(t, err) } else { @@ -243,6 +263,7 @@ func TestPutHandler(t *testing.T) { if !tt.expectError { require.Equal(t, []byte(tt.expectedBody), rec.Body.Bytes()) } + require.Equal(t, tt.expectedCommitmentMeta, meta) }) } }