feat(price-pusher): allow multiple injective gRPC endpoints with failover#3703
feat(price-pusher): allow multiple injective gRPC endpoints with failover#37030xghost42 wants to merge 3 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 7 Skipped Deployments
|
|
Pushed Heads-up on coordination: #3689 (mnemonic env var) also bumps to Re: the additional 5 findings hidden in the Devin Review console — I can't see them from the GitHub PR view, so happy to address any that surface here as separate comments. |
|
Pushed "./injective/endpoint-pool": {
"default": "./dist/injective/endpoint-pool.cjs",
"types": "./dist/injective/endpoint-pool.d.ts"
}Confirmed the pattern matches every other source file in Lockfile / dependencies untouched. |
The sample Grafana dashboard (`grafana-dashboard.sample.json`) filters
every Prometheus query by `namespace=$chain`:
pyth_price_feeds_total{namespace="$chain"}
pyth_price_last_published_time{namespace="$chain"}
pyth_price_update_attempts_total{namespace="$chain", ...}
...
But `src/metrics.ts` only set `app="price_pusher"` as the default label
and never emitted a `namespace` label on any counter/gauge, and
`prometheus.sample.yml` did not add one via relabeling. With the
dashboard out of the box that filter matched zero series, so every
panel for a fresh deployment came up empty — flagged by Devin during
review of pyth-network#3692 and acknowledged there as pre-existing dashboard
behavior worth fixing in a separate PR. This is that PR.
Wiring:
- new CLI option `--metrics-namespace <name>` in `src/options.ts`,
defaulted per chain command (`evm`, `sui`, `aptos`, `solana`) so
that single-chain deployments work without configuration
- `PricePusherMetrics` constructor now takes a `namespace: string`
parameter and sets it via `registry.setDefaultLabels`, so every
existing metric series gains a `namespace` label without per-metric
`labelNames` plumbing
- each chain command (`evm/sui/aptos/solana/command.ts`) reads the
new arg and passes through to the constructor
Operators running multiple deployments of the same chain (e.g. several
EVM networks against one Grafana instance) can now set
`--metrics-namespace bsc-mainnet` / `--metrics-namespace polygon-mainnet`
to disambiguate. Single-deployment setups keep working unchanged.
Bumps `@pythnetwork/price-pusher` to `10.5.0` (additive, no breaking
changes — old CLIs still work). Note: pyth-network#3689 and pyth-network#3703 also bump to
`10.5.0`, so whichever of the three lands second/third will need a
trivial rebase to `10.6.0` / `10.7.0`.
Refs pyth-network#3692.
…over Closes pyth-network#1014. The injective price pusher accepted a single `--grpc-endpoint`. Cosmos nodes are occasionally flaky, so operators ended up running one pusher process per endpoint just to get redundancy. Maintainer (jayantk) said on the issue that cycling chain endpoints was the right shape. - `--grpc-endpoint` is now `type: "array"`, accepting either repeated flag occurrences or a comma-separated list (the handler splits + trims each entry). A single endpoint still works exactly as before. Empty input is rejected up front with a clear message. - New `src/injective/endpoint-pool.ts`: - `EndpointPool` — round-robin cursor over a non-empty endpoint list, shared across every gRPC API helper in the pusher so a bad endpoint affects the whole instance, not just one method. - `withEndpointFailover(pool, fn)` — runs `fn(currentEndpoint)`, rotates on failure, walks every endpoint at most once before re-throwing the last error. - `InjectivePriceListener` + `InjectivePricePusher` constructors take `string | readonly string[]` for backward compatibility, wrap each gRPC call site (`ChainGrpcWasmApi.fetchSmartContractState`, `ChainGrpcAuthApi.fetchAccount`, `TxGrpcApi.broadcast`, `TxGrpcApi.simulate`) in `withEndpointFailover`. - README: short note on the new fallback-set syntax under the Injective example. - `pnpm --filter @pythnetwork/price-pusher exec test-unit` — 8 cases, all pass: empty rejection, default cursor, round-robin wrap, single-endpoint no-op, success no-rotate, single-failure rotate, exhaust-and-throw, cursor-position respected by `withEndpointFailover`. - `pnpm --filter @pythnetwork/price-pusher exec tsc --noEmit` clean. - Wired `test:unit` into the package scripts so the workspace test runner picks the new file up. The original issue also mentioned the Pyth price service (Hermes) gRPC endpoint as a possibly useful failover target. That's a different code path (HermesClient takes a single URL today), so I kept this PR focused on the chain-endpoint case the reporter named primarily. Happy to follow up on the Hermes side in a separate PR if the maintainers want it.
The exports map drives ts-duality --noEsm CJS compilation. Without an entry for src/injective/endpoint-pool.ts, dist/injective/endpoint-pool.cjs is not generated and dist/injective/injective.cjs fails at runtime when it requires ./endpoint-pool.js. Mirror the pattern used by the other injective files.
342273d to
d8aadbf
Compare
| "type": "module", | ||
| "types": "./dist/index.d.ts", | ||
| "version": "10.4.0" | ||
| "version": "10.5.0" |
There was a problem hiding this comment.
🚩 Version bump present but lockfile not updated
The package version was bumped from 10.4.0 to 10.5.0 (minor, appropriate for new functionality), but the pnpm-lock.yaml was not updated in this PR. Per REVIEW.md guidelines, lock file changes should accompany version bumps. This may need to be addressed before merge or as part of the merge process.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Closes #1014.
The injective price pusher only accepted a single
--grpc-endpoint. Cosmos nodes are occasionally flaky, so operators ended up spinning up one pusher process per endpoint just to get redundancy. Maintainer (@jayantk) confirmed on the original issue that cycling chain endpoints was the right shape.This PR makes
--grpc-endpointa fallback set: the pusher round-robins to the next endpoint whenever a gRPC call fails, walking every endpoint at most once before re-throwing.Change
--grpc-endpointis nowtype: "array". Accepts either repeated flag occurrences (--grpc-endpoint a --grpc-endpoint b) or comma-separated values (--grpc-endpoint a,b,c). A single endpoint still works exactly as before. Empty input rejected up front with a clear message.apps/price_pusher/src/injective/endpoint-pool.ts:EndpointPool— round-robin cursor over a non-empty endpoint list, shared across every gRPC API helper in a pusher instance so a bad endpoint affects the whole pusher, not just one method.withEndpointFailover(pool, fn)— runsfn(currentEndpoint), rotates on failure, walks every endpoint at most once before re-throwing the last error.InjectivePriceListener+InjectivePricePusherconstructors takestring | readonly string[]for backward compatibility and wrap each gRPC call site (ChainGrpcWasmApi.fetchSmartContractState,ChainGrpcAuthApi.fetchAccount,TxGrpcApi.broadcast,TxGrpcApi.simulate) inwithEndpointFailover.Verification
pnpm --filter @pythnetwork/price-pusher exec test-unit— 8 cases, all pass:withEndpointFailoverreturns first successful result without rotatingpnpm --filter @pythnetwork/price-pusher exec tsc --noEmitclean.test:unitintopackage.jsonscripts so the workspace test runner picks the new file up.Out of scope
The original issue also mentioned the Pyth price service (Hermes) gRPC endpoint as a possibly useful failover target. That's a different code path (
HermesClienttakes a single URL today), so I kept this PR focused on the chain-endpoint case @ewoolsey named primarily. Happy to follow up on the Hermes side in a separate PR if the maintainers want it.