diff --git a/helm/charts/erpc/Chart.yaml b/helm/charts/erpc/Chart.yaml index ecc04b052..795900ae0 100644 --- a/helm/charts/erpc/Chart.yaml +++ b/helm/charts/erpc/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.35 +version: 0.1.36 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/helm/charts/erpc/README.md b/helm/charts/erpc/README.md index 34f5dc5d6..f09e5abc4 100644 --- a/helm/charts/erpc/README.md +++ b/helm/charts/erpc/README.md @@ -34,10 +34,24 @@ curl --location 'http://localhost:4000/cache/evm/1?secret=YOUR-AUTH-SECRET' \ --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' ``` -## Vault Config +## Config Sources -The eRPC chart reads non-secret config from `vault.configPath` and secret values -from `vault.secretsPath`. Keep `vault.configPath` as a config template. Static +The eRPC chart reads the non-secret config template from either: + +- a Kubernetes ConfigMap (key `erpc.yaml`) named by `vault.configMapName` + (preferred, PLA-2003: config lives in the repo and goes through PR review), or +- Vault at `vault.configPath` when `vault.configMapName` is empty (legacy). + +Secret values always come from Vault at `vault.secretsPath`; only secrets +(passwords, API keys) and feature flags belong there. When `vault.configMapName` +is set, the ConfigMap must be created before the `vault-config-creator` job runs +— in the prd environment chart, files under `config/*.yaml` are rendered into +`-config` ConfigMaps as pre-install/pre-upgrade hooks with an earlier +hook-weight/sync-wave than the job. + +## Vault Secrets + +Keep the config source (ConfigMap or `vault.configPath`) as a template. Static secret placeholders use `__SECRET___`; the API-key auth list can use the `__SECRET_API_KEY_STRATEGIES__` marker to generate one secret auth strategy for every `API_KEY_*=` entry in `vault.secretsPath`. Generated auth IDs are @@ -58,3 +72,14 @@ values. The Vault config job renders placeholders into a temporary `erpc.yaml`, validates that rendered config with `erpc validate`, then stores only the rendered file in the runtime Kubernetes Secret. + +### Migrating an instance from Vault config to ConfigMap + +1. Export the instance's config from Vault (`.Data.data.config` at its + `vault.configPath`) into `helm/environments/prd/erpc/config/.yaml`. +2. Replace any inline secret values (for example database or Redis passwords in + connection URIs) with `__SECRET___` placeholders, and add the matching + `KEY=VALUE` entries to `vault.secretsPath` in Vault **before** deploying — + the render job fails hard on unresolved placeholders. +3. Set `vault.configMapName: "-config"` on the instance's values and bump + the chart versions. diff --git a/helm/charts/erpc/templates/job-vault-config.yaml b/helm/charts/erpc/templates/job-vault-config.yaml index bf1d58ada..d9e5d7346 100644 --- a/helm/charts/erpc/templates/job-vault-config.yaml +++ b/helm/charts/erpc/templates/job-vault-config.yaml @@ -3,6 +3,8 @@ {{- fail "vault.validationImage.repository and vault.validationImage.tag are required when vault.validationImage.enabled=true" }} {{- end }} {{- $useDedicatedValidator := $validationImage.enabled }} +{{- $configMapName := .Values.vault.configMapName | default "" }} +{{- $configTemplatePath := ternary "/erpc-config/erpc.yaml" "/vault/secrets/erpc-config" (ne $configMapName "") }} apiVersion: batch/v1 kind: Job metadata: @@ -22,15 +24,18 @@ spec: {{- include "erpc.selectorLabels" . | nindent 8 }} job: vault-config-creator annotations: - # Vault Agent annotations for config injection (on pod template) + # Vault Agent annotations for secrets (and, without configMapName, + # config) injection (on pod template) vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: {{ include "erpc.vaultCreatorServiceAccountName" . | quote }} + {{- if not $configMapName }} vault.hashicorp.com/agent-inject-secret-erpc-config: {{ .Values.vault.configPath | default "secret/data/erpc/config" | quote }} - vault.hashicorp.com/agent-inject-secret-erpc-secrets: {{ .Values.vault.secretsPath | default "secret/data/erpc/secrets" | quote }} vault.hashicorp.com/agent-inject-template-erpc-config: | {{`{{- with secret "`}}{{ .Values.vault.configPath | default "secret/data/erpc/config" }}{{`" -}} {{- .Data.data.config -}} {{- end }}`}} + {{- end }} + vault.hashicorp.com/agent-inject-secret-erpc-secrets: {{ .Values.vault.secretsPath | default "secret/data/erpc/secrets" | quote }} vault.hashicorp.com/agent-inject-template-erpc-secrets: | {{`{{- with secret "`}}{{ .Values.vault.secretsPath | default "secret/data/erpc/secrets" }}{{`" -}} {{- range $key, $value := .Data.data -}} @@ -50,6 +55,11 @@ spec: volumes: - name: rendered-config emptyDir: {} + {{- if $configMapName }} + - name: erpc-config-template + configMap: + name: {{ $configMapName | quote }} + {{- end }} {{- if .Values.securityHardening.enabled }} - name: tmp emptyDir: {} @@ -64,14 +74,21 @@ spec: imagePullPolicy: {{ .Values.vault.jobImage.pullPolicy }} resources: {{- toYaml .Values.vault.jobResources | nindent 12 }} - {{- if .Values.securityHardening.enabled }} env: + - name: CONFIG_TEMPLATE_PATH + value: {{ $configTemplatePath | quote }} + {{- if .Values.securityHardening.enabled }} - name: HOME value: /tmp - {{- end }} + {{- end }} volumeMounts: - name: rendered-config mountPath: /work + {{- if $configMapName }} + - name: erpc-config-template + mountPath: /erpc-config + readOnly: true + {{- end }} {{- if .Values.securityHardening.enabled }} - name: tmp mountPath: /tmp @@ -85,18 +102,18 @@ spec: set -o pipefail fi - echo "Rendering ERPC configuration from vault config and secrets..." + echo "Rendering ERPC configuration from config template $CONFIG_TEMPLATE_PATH and vault secrets..." timeout=60 count=0 - while { [ ! -f /vault/secrets/erpc-config ] || [ ! -f /vault/secrets/erpc-secrets ]; } && [ $count -lt $timeout ]; do - echo "Waiting for vault config and secrets files... ($count/$timeout)" + while { [ ! -f "$CONFIG_TEMPLATE_PATH" ] || [ ! -f /vault/secrets/erpc-secrets ]; } && [ $count -lt $timeout ]; do + echo "Waiting for config template and vault secrets files... ($count/$timeout)" sleep 2 count=$((count + 2)) done - if [ ! -f /vault/secrets/erpc-config ]; then - echo "ERROR: Vault config file not found after $timeout seconds" + if [ ! -f "$CONFIG_TEMPLATE_PATH" ]; then + echo "ERROR: Config template file $CONFIG_TEMPLATE_PATH not found after $timeout seconds" exit 1 fi @@ -346,7 +363,7 @@ spec: exit 1 } } - ' /vault/secrets/erpc-config > "$tmp_rendered_config" + ' "$CONFIG_TEMPLATE_PATH" > "$tmp_rendered_config" mv "$tmp_rendered_config" "$rendered_config" echo "ERPC configuration rendered to $rendered_config" diff --git a/helm/charts/erpc/values.yaml b/helm/charts/erpc/values.yaml index 3f290978f..f2626dcdd 100644 --- a/helm/charts/erpc/values.yaml +++ b/helm/charts/erpc/values.yaml @@ -8,6 +8,13 @@ vault: # Non-secret eRPC config template. Secret placeholders use __SECRET___; # the API-key auth list can be generated from __SECRET_API_KEY_STRATEGIES__. configPath: "secret/data/erpc/config" + # When set, the non-secret config template is read from this ConfigMap + # (key "erpc.yaml") instead of Vault's configPath. Secrets are still + # injected from secretsPath and substituted into __SECRET___ + # placeholders. The ConfigMap must be created before the + # vault-config-creator job runs (pre-install/pre-upgrade hook with a + # lower hook-weight / earlier sync-wave than the job). + configMapName: "" # Dedicated secret-only entry shared by all eRPC config variants. secretsPath: "secret/data/erpc/secrets" jobImage: diff --git a/helm/environments/prd/erpc/Chart.lock b/helm/environments/prd/erpc/Chart.lock index ed8c8bf25..682f03c79 100644 --- a/helm/environments/prd/erpc/Chart.lock +++ b/helm/environments/prd/erpc/Chart.lock @@ -1,19 +1,19 @@ dependencies: - name: erpc repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: erpc repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: erpc repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: erpc repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: erpc repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: redis-ha repository: https://dandydeveloper.github.io/charts version: 4.35.2 @@ -29,5 +29,5 @@ dependencies: - name: prometheus-redis-exporter repository: https://prometheus-community.github.io/helm-charts version: 6.8.0 -digest: sha256:6f63ffe63edeae52172c50f6d1dd3ed8f12e964a7c8af088c49daeeb4f2a5cda -generated: "2026-06-16T17:08:14.874547+02:00" +digest: sha256:c0eab2e9c69ceeab79ea64c4154d2ec810eae54d0e0090cd39fffc953e08a00f +generated: "2026-07-21T17:28:33.268154+02:00" diff --git a/helm/environments/prd/erpc/Chart.yaml b/helm/environments/prd/erpc/Chart.yaml index 054078259..5a3ba1103 100644 --- a/helm/environments/prd/erpc/Chart.yaml +++ b/helm/environments/prd/erpc/Chart.yaml @@ -2,33 +2,33 @@ apiVersion: v2 name: erpc-prd description: eRPC fault-tolerant EVM RPC proxy - Production Environment type: application -version: 0.1.40 +version: 0.1.41 appVersion: "latest" dependencies: - name: erpc repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: erpc alias: erpc-dev condition: erpc-dev.enabled repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: erpc alias: erpc-processing condition: erpc-processing.enabled repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: erpc alias: erpc-router condition: erpc-router.enabled repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: erpc alias: erpc-fallback condition: erpc-fallback.enabled repository: file://../../../charts/erpc - version: 0.1.35 + version: 0.1.36 - name: redis-ha version: "4.35.2" repository: "https://dandydeveloper.github.io/charts" diff --git a/helm/environments/prd/erpc/charts/erpc-0.1.35.tgz b/helm/environments/prd/erpc/charts/erpc-0.1.35.tgz deleted file mode 100644 index eb0368df3..000000000 Binary files a/helm/environments/prd/erpc/charts/erpc-0.1.35.tgz and /dev/null differ diff --git a/helm/environments/prd/erpc/charts/erpc-0.1.36.tgz b/helm/environments/prd/erpc/charts/erpc-0.1.36.tgz new file mode 100644 index 000000000..ee4e344c2 Binary files /dev/null and b/helm/environments/prd/erpc/charts/erpc-0.1.36.tgz differ diff --git a/helm/environments/prd/erpc/config/erpc.yaml b/helm/environments/prd/erpc/config/erpc.yaml new file mode 100644 index 000000000..788eb8ae0 --- /dev/null +++ b/helm/environments/prd/erpc/config/erpc.yaml @@ -0,0 +1,817 @@ +logLevel: error +tracing: + enabled: true + endpoint: "otel-collector.opentelemetry.svc.cluster.local:4317" + protocol: "grpc" + sampleRate: 0.01 + detailed: true +server: + listenV4: true + httpHostV4: "0.0.0.0" + httpPortV4: 4000 + maxTimeout: "120s" + executionHeaders: summary +healthCheck: + mode: simple + defaultEval: "any:initializedUpstreams" +metrics: + enabled: true + listenV4: true + hostV4: "0.0.0.0" + port: 4001 + errorLabelMode: compact + histogramBuckets: "0.05,0.5,5,30" + histogramDropLabels: + - agent_name + - composite +rateLimiters: + store: + driver: redis + redis: + uri: "redis://default:__SECRET_REDIS_PASSWORD__@erpc-redis-haproxy:6379" + budgets: + - id: lite-app + rules: + - method: "*" + maxCount: 20 + period: 1s + - id: quicknode-budget + rules: + - method: "*" + maxCount: 500 + period: 1s +database: + evmJsonRpcCache: + envelope: true + compression: + enabled: true + algorithm: zstd + zstdLevel: fastest + threshold: 1024 + connectors: + - id: "postgres-connector" + driver: "postgresql" + postgresql: &postgres-config + connectionUri: "postgres://postgres:__SECRET_POSTGRES_PASSWORD__@erpc-db-rw.morpho-prd.svc.cluster.local:5432/erpc" + table: rpc_cache + initTimeout: 5s + getTimeout: 5s + setTimeout: 5s + minConns: 2 + maxConns: 15 + readonlyConnectionUris: + - postgres://postgres:__SECRET_POSTGRES_PASSWORD__@erpc-db-r.morpho-prd.svc.cluster.local:5432/erpc + - id: "redis-connector" + driver: "redis" + redis: + uri: "redis://default:__SECRET_REDIS_PASSWORD__@erpc-redis-haproxy:6379" + getTimeout: 3s + setTimeout: 3s + policies: + # =========================================== + # Method-specific policies with numeric blocks + # These have longer TTLs because data at a specific block is immutable + # =========================================== + + # Don't cache null/empty block responses in case nodes are late + - network: "*" + method: "eth_getBlockByNumber" + finality: "finalized" + empty: "ignore" + connector: "postgres-connector" + ttl: 0 + # eth_call with numeric blocks - unfinalized + - network: "*" + method: "eth_call" + params: ["*", "0x*"] + finality: "unfinalized" + connector: "redis-connector" + ttl: 60s + # eth_getStorageAt with numeric blocks - unfinalized + - network: "*" + method: "eth_getStorageAt" + params: ["*", "*", "0x*"] + finality: "unfinalized" + connector: "redis-connector" + ttl: 60s + # eth_getBalance with numeric blocks - unfinalized + - network: "*" + method: "eth_getBalance" + params: ["*", "0x*"] + finality: "unfinalized" + connector: "redis-connector" + ttl: 60s + # eth_getBlockByNumber with numeric blocks - unfinalized + - network: "*" + method: "eth_getBlockByNumber" + params: ["0x*", "*"] + finality: "unfinalized" + connector: "redis-connector" + ttl: 60s + # eth_getCode with numeric blocks - unfinalized + - network: "*" + method: "eth_getCode" + params: ["*", "0x*"] + finality: "unfinalized" + connector: "redis-connector" + ttl: 60s + # eth_chainId - never changes + - network: "*" + method: "eth_chainId" + finality: "unknown" + connector: "postgres-connector" + ttl: 0 + # =========================================== + # Default policies by finality + # =========================================== + - network: "*" + method: "*" + finality: "finalized" + empty: "allow" + connector: "postgres-connector" + ttl: 0 + - network: "*" + method: "*" + finality: "unfinalized" + connector: "redis-connector" + ttl: 30s + - network: "*" + method: "*" + finality: "realtime" + connector: "redis-connector" + ttl: + blockTimeMultiplier: 1 + fallback: 2s + - network: "*" + method: "*" + finality: "unknown" + connector: "redis-connector" + ttl: 5s + sharedState: + clusterKey: "erpc-morpho-cluster" + connector: + driver: "redis" + redis: + uri: "redis://default:__SECRET_REDIS_PASSWORD__@erpc-redis-haproxy:6379" +projects: + - id: cache + scoreMetricsWindowSize: 4m + cors: &shared-cors + allowedOrigins: + - "https://*.morpho.org" + - "https://*.morpho.dev" + - "https://*morpho-blue-offchain-v2-server.netlify.app" + - "https://*-morpho-addmo.vercel.app" + allowedMethods: + - "GET" + - "POST" + - "OPTIONS" + allowedHeaders: + - "*" + allowCredentials: true + maxAge: 3600 + auth: &shared-auth + strategies: + - type: database + database: + connector: + id: auth-postgresql + driver: postgresql + postgresql: + connectionUri: postgres://postgres:__SECRET_POSTGRES_PASSWORD__@erpc-db-rw.morpho-prd.svc.cluster.local:5432/erpc + table: erpc_auth + networks: + - architecture: evm + evm: + chainId: 143 + integrity: + enforceHighestBlock: true + failsafe: + - matchMethod: "eth_call" + matchFinality: + - realtime + - unfinalized + timeout: + duration: "15s" + retry: + maxAttempts: 2 + delay: "250ms" + backoffMaxDelay: "1s" + backoffFactor: 2 + emptyResultDelay: "100ms" + hedge: ~ + - architecture: evm + evm: + chainId: 2741 + directiveDefaults: + # Abstract returns zero tx roots for non-empty blocks. + validateTransactionsRoot: false + upstreamDefaults: &upstream-defaults + evm: + statePollerInterval: 10s + statePollerDebounce: 5s + failsafe: + timeout: + duration: "120s" + retry: + maxAttempts: 2 + delay: "500ms" + backoffMaxDelay: "5s" + backoffFactor: 3 + hedge: + delay: + base: "2s" + quantile: 0.95 + min: "1s" + max: "5s" + maxCount: 1 + circuitBreaker: + failureThresholdCount: 10 + failureThresholdCapacity: 20 + halfOpenAfter: "15s" + successThresholdCount: 3 + successThresholdCapacity: 5 + routing: + scoreMultipliers: + - network: "*" + method: "*" + totalRequests: 0 # Disabled - pure performance mode + respLatency: 8.0 # Highest weight - fastest wins + errorRate: 4.0 # Penalize errors + misbehaviors: 5.0 # Penalize consensus violations + throttledRate: 3.0 # Penalize rate limits + blockHeadLag: 2.0 # Penalize stale block head + finalizationLag: 1.0 # Lowest weight + upstreams: &shared-upstreams + - id: camp + endpoint: https://rpc-mainnet.campnetwork.xyz + evm: + chainId: 484 + - id: bitlayer + endpoint: https://rpc.bitlayer.org + evm: + chainId: 200901 + - id: alchemy-robinhood-mainnet + endpoint: https://robinhood-mainnet.g.alchemy.com/v2/__SECRET_UPSTREAM_ALCHEMY_ROBINHOOD_MAINNET_ALCHEMY_KEY__ + vendorName: alchemy + evm: + chainId: 4663 + - id: alchemy-arc-mainnet + endpoint: https://arc-mainnet.g.alchemy.com/v2/__SECRET_UPSTREAM_ALCHEMY_ROBINHOOD_MAINNET_ALCHEMY_KEY__ + vendorName: alchemy + evm: + chainId: 5042 + providers: &shared-providers + - id: alchemy + vendor: alchemy + settings: + apiKey: "__SECRET_UPSTREAM_ALCHEMY_ROBINHOOD_MAINNET_ALCHEMY_KEY__" + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:10": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:137": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:324": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:480": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:1101": + evm: + getLogsAutoSplittingRangeThreshold: 20000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:143": + evm: + getLogsAutoSplittingRangeThreshold: 1000 + routing: + scoreMultipliers: &monad-alchemy-143-routing + - method: "eth_call|eth_getBalance|eth_getCode|eth_getBlockByNumber" + finality: + - realtime + - unfinalized + overall: 0.8 + totalRequests: 0 + respLatency: 2.0 + errorRate: 5.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 25.0 + finalizationLag: 3.0 + - method: "*" + totalRequests: 0 + respLatency: 2.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 10.0 + finalizationLag: 2.0 + "evm:42161": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:8453": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:80094": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:9745": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:988": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + - id: quicknode + vendor: quicknode + ignoreNetworks: + - evm:988 + settings: + apiKey: "__SECRET_PROVIDER_QUICKNODE_APIKEY__" + overrides: + "*": + rateLimitBudget: quicknode-budget + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1329": + rateLimitBudget: quicknode-budget + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:143": + rateLimitBudget: quicknode-budget + routing: + scoreMultipliers: &monad-quicknode-143-routing + - method: "eth_call|eth_getBalance|eth_getCode|eth_getBlockByNumber" + finality: + - realtime + - unfinalized + overall: 2.5 + totalRequests: 0 + respLatency: 2.0 + errorRate: 5.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 25.0 + finalizationLag: 1.0 + - method: "*" + totalRequests: 0 + respLatency: 2.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 10.0 + finalizationLag: 2.0 + - id: erpc + vendor: erpc + settings: + endpoint: edge.goldsky.com/standard/evm + secret: "__SECRET_PROVIDER_ERPC_SECRET__" + ignoreNetworks: + - evm:143 + - evm:484 + - evm:988 + - evm:200901 + - evm:999 + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 30000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:143": + routing: + scoreMultipliers: + - method: "*" + totalRequests: 0 + respLatency: 2.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 10.0 + finalizationLag: 2.0 + "evm:239": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + - id: drpc + vendor: drpc + settings: + apiKey: "__SECRET_PROVIDER_DRPC_APIKEY__" + ignoreNetworks: + - evm:988 + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:143": + routing: + scoreMultipliers: &monad-drpc-143-routing + - method: "eth_call|eth_getBalance|eth_getCode|eth_getBlockByNumber" + finality: + - realtime + - unfinalized + overall: 0.25 + totalRequests: 0 + respLatency: 2.0 + errorRate: 6.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 20.0 + finalizationLag: 8.0 + - method: "*" + totalRequests: 0 + respLatency: 2.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 10.0 + finalizationLag: 2.0 + - id: conduit + vendor: conduit + settings: + apiKey: "__SECRET_PROVIDER_CONDUIT_APIKEY__" + onlyNetworks: + - evm:747474 + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + - id: ankr + vendor: ankr + settings: + apiKey: "__SECRET_PROVIDER_ANKR_APIKEY__" + onlyNetworks: + - evm:14 + - evm:239 + - evm:42793 + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:239": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + networkDefaults: &shared-network-defaults + selectionPolicy: + evalScope: network-method-finality + evalInterval: 15s + evalTimeout: 100ms + evm: + servedTip: + enabledFor: + - latest + - finalized + integrity: + enforceGetLogsBlockRange: true + enforceHighestBlock: false + getLogsSplitOnError: true + # Tank spikes: parallelize getLogs split sub-queries (split >> chunk). + getLogsSplitConcurrency: 200 + # Cache-chunk concurrency: keep lower than split to avoid DB/Redis stampede. + getLogsCacheChunkConcurrency: 50 + # Cap decompressed upstream response size; TooLarge triggers split instead of buffering/OOM. + getLogsMaxResponseBytes: 67108864 + getLogsMaxAllowedRange: 150001 + multicall3Aggregation: + enabled: true + windowMs: 50 + minWaitMs: 5 + maxCalls: 100 + maxCalldataBytes: 262144 + maxQueueSize: 5000 + maxPendingBatches: 500 + cachePerCall: true + allowCrossUserBatching: true + allowPendingTagBatching: false + failsafe: + timeout: + duration: "120s" + retry: + maxAttempts: 4 + delay: "1s" + backoffMaxDelay: "10s" + backoffFactor: 3 + emptyResultDelay: "200ms" + hedge: + delay: + base: "2s" + quantile: 0.95 + min: "1s" + max: "5s" + maxCount: 1 + - id: realtime + scoreMetricsWindowSize: 4m + cors: *shared-cors + auth: *shared-auth + networks: + - architecture: evm + evm: + chainId: 143 + integrity: + enforceHighestBlock: true + failsafe: + - matchMethod: "eth_call" + matchFinality: + - realtime + - unfinalized + timeout: + duration: "15s" + retry: + maxAttempts: 2 + delay: "250ms" + backoffMaxDelay: "1s" + backoffFactor: 2 + emptyResultDelay: "100ms" + hedge: ~ + upstreamDefaults: *upstream-defaults + upstreams: *shared-upstreams + providers: *shared-providers + networkDefaults: + !!merge <<: *shared-network-defaults + directiveDefaults: + skipCacheRead: true + - id: experimental + scoreMetricsMode: detailed + scoreGranularity: method + scoreMetricsWindowSize: 4m + cors: *shared-cors + auth: *shared-auth + networks: + - architecture: evm + evm: + chainId: 143 + integrity: + enforceHighestBlock: true + failsafe: + - matchMethod: "eth_call" + matchFinality: + - realtime + - unfinalized + timeout: + duration: "15s" + retry: + maxAttempts: 2 + delay: "250ms" + backoffMaxDelay: "1s" + backoffFactor: 2 + emptyResultDelay: "100ms" + hedge: ~ + upstreamDefaults: *upstream-defaults + upstreams: *shared-upstreams + providers: + # Include all shared providers + - id: alchemy + vendor: alchemy + settings: + apiKey: "__SECRET_UPSTREAM_ALCHEMY_ROBINHOOD_MAINNET_ALCHEMY_KEY__" + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:10": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:137": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:324": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:480": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:1101": + evm: + getLogsAutoSplittingRangeThreshold: 20000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:143": + evm: + getLogsAutoSplittingRangeThreshold: 1000 + routing: + scoreMultipliers: *monad-alchemy-143-routing + "evm:42161": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:8453": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:80094": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:9745": + evm: + getLogsAutoSplittingRangeThreshold: 100000 + "evm:988": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + - id: quicknode + vendor: quicknode + ignoreNetworks: + - evm:988 + settings: + apiKey: "__SECRET_PROVIDER_QUICKNODE_APIKEY__" + overrides: + "*": + rateLimitBudget: quicknode-budget + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1329": + rateLimitBudget: quicknode-budget + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:143": + rateLimitBudget: quicknode-budget + routing: + scoreMultipliers: *monad-quicknode-143-routing + - id: erpc + vendor: erpc + settings: + endpoint: edge.goldsky.com/standard/evm + secret: "__SECRET_PROVIDER_ERPC_SECRET_77053707__" + ignoreNetworks: + - evm:143 + - evm:484 + - evm:988 + - evm:200901 + - evm:999 + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 30000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:143": + routing: + scoreMultipliers: + - method: "*" + totalRequests: 0 + respLatency: 2.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 10.0 + finalizationLag: 2.0 + "evm:239": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + - id: drpc + vendor: drpc + settings: + apiKey: "__SECRET_PROVIDER_DRPC_APIKEY__" + ignoreNetworks: + - evm:988 + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:143": + routing: + scoreMultipliers: *monad-drpc-143-routing + - id: conduit + vendor: conduit + settings: + apiKey: "__SECRET_PROVIDER_CONDUIT_APIKEY__" + onlyNetworks: + - evm:747474 + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + - id: ankr + vendor: ankr + settings: + apiKey: "__SECRET_PROVIDER_ANKR_APIKEY__" + onlyNetworks: + - evm:14 + - evm:239 + - evm:42793 + overrides: + "*": + evm: + getLogsAutoSplittingRangeThreshold: 10000 + "evm:1329": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + "evm:239": + evm: + getLogsAutoSplittingRangeThreshold: 2000 + # SQD Portal - force eth_getLogs, trace_block to SQD for supported chains + # Batch settings (supportsBatch, batchMaxWait: 1ns, batchMaxSize: 1000) are set by default + # NOTE: routing.scoreMultipliers in overrides fully replaces upstreamDefaults.routing, + # so component multipliers must be repeated here alongside overall. + - id: sqd-portal + vendor: sqd + settings: + endpoint: "https://morpho.portal.sqd.dev/rpc/v1/evm/{chainId}" + apiKey: "__SECRET_PROVIDER_SQD_PORTAL_APIKEY__" + onlyNetworks: + - evm:1 # Ethereum + - evm:8453 # Base + - evm:137 # Polygon + - evm:42161 # Arbitrum + - evm:10 # Optimism + - evm:1301 # Unichain + - evm:998 # Hyperliquid + - evm:747474 # Katana + - evm:480 # Worldchain + overrides: + "*": + routing: + scoreMultipliers: + - method: "eth_chainId" + overall: 100 + totalRequests: 0 + respLatency: 8.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 2.0 + finalizationLag: 1.0 + - method: "eth_blockNumber" + overall: 100 + totalRequests: 0 + respLatency: 8.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 2.0 + finalizationLag: 1.0 + - method: "eth_getBlockByNumber" + overall: 100 + totalRequests: 0 + respLatency: 8.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 2.0 + finalizationLag: 1.0 + - method: "eth_getTransactionByBlockNumberAndIndex" + overall: 100 + totalRequests: 0 + respLatency: 8.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 2.0 + finalizationLag: 1.0 + - method: "eth_getLogs" + overall: 1000 + totalRequests: 0 + respLatency: 8.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 2.0 + finalizationLag: 1.0 + - method: "trace_block" + overall: 1000 + totalRequests: 0 + respLatency: 8.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 2.0 + finalizationLag: 1.0 + - method: "*" + totalRequests: 0 + respLatency: 8.0 + errorRate: 4.0 + misbehaviors: 5.0 + throttledRate: 3.0 + blockHeadLag: 2.0 + finalizationLag: 1.0 + networkDefaults: + !!merge <<: *shared-network-defaults + directiveDefaults: + cacheMaxAgeSeconds: 60 + diff --git a/helm/environments/prd/erpc/templates/configmap-erpc-config.yaml b/helm/environments/prd/erpc/templates/configmap-erpc-config.yaml new file mode 100644 index 000000000..e227b29f2 --- /dev/null +++ b/helm/environments/prd/erpc/templates/configmap-erpc-config.yaml @@ -0,0 +1,33 @@ +{{/* +Render one ConfigMap per eRPC config file under config/. The file basename +becomes the ConfigMap name suffixed with "-config" (config/erpc.yaml -> +ConfigMap "erpc-config"); each erpc subchart instance points at its ConfigMap +via vault.configMapName. + +These ConfigMaps hold the non-secret eRPC configuration (PLA-2003). Secret +values stay in Vault (secret/data/erpc/secrets) and are substituted into the +__SECRET___ placeholders by the vault-config-creator job. + +Rendered as a pre-install/pre-upgrade hook (weight 0, sync-wave -4) so the +ConfigMap is updated before the vault-config-creator job (weight 1, +sync-wave -3) renders the final config Secret. +*/}} +{{- range $path, $bytes := .Files.Glob "config/*.yaml" }} +{{- $name := trimSuffix ".yaml" (base $path) }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-config" $name }} + labels: + app.kubernetes.io/name: {{ printf "%s-config" $name }} + app.kubernetes.io/instance: {{ $.Release.Name }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "0" + "helm.sh/hook-delete-policy": before-hook-creation + argocd.argoproj.io/sync-wave: "-4" +data: + erpc.yaml: | +{{ $.Files.Get $path | indent 4 }} +{{- end }} diff --git a/helm/environments/prd/erpc/values.yaml b/helm/environments/prd/erpc/values.yaml index b26311167..8de499dbe 100644 --- a/helm/environments/prd/erpc/values.yaml +++ b/helm/environments/prd/erpc/values.yaml @@ -1383,6 +1383,11 @@ erpc: autovacuumMaxWorkers: "4" vault: + # PLA-2003: non-secret config now lives in the repo + # (helm/environments/prd/erpc/config/erpc.yaml -> ConfigMap "erpc-config"). + # Vault keeps only secrets (secret/data/erpc/secrets) substituted into + # __SECRET___ placeholders. + configMapName: "erpc-config" jobImage: repository: 537124939463.dkr.ecr.eu-west-3.amazonaws.com/erpc-validator tag: &erpcValidatorImageTag "0.1.3"