Skip to content

feat(price-pusher): allow multiple injective gRPC endpoints with failover#3703

Open
0xghost42 wants to merge 3 commits into
pyth-network:mainfrom
0xghost42:feat/1014-grpc-endpoints
Open

feat(price-pusher): allow multiple injective gRPC endpoints with failover#3703
0xghost42 wants to merge 3 commits into
pyth-network:mainfrom
0xghost42:feat/1014-grpc-endpoints

Conversation

@0xghost42
Copy link
Copy Markdown

@0xghost42 0xghost42 commented May 14, 2026

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-endpoint a 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-endpoint is now type: "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.
  • New 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) — 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 and 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.

Verification

  • pnpm --filter @pythnetwork/price-pusher exec test-unit — 8 cases, all pass:
    • empty endpoint list rejected
    • default cursor returns first endpoint
    • round-robin rotation wraps past the end
    • single-endpoint pool: rotate is a no-op
    • withEndpointFailover returns first successful result without rotating
    • rotates and returns next-endpoint's result on single failure
    • exhausts the pool and re-throws the last error
    • respects the pool's current cursor position when starting
  • pnpm --filter @pythnetwork/price-pusher exec tsc --noEmit clean.
  • Wired test:unit into package.json scripts 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 (HermesClient takes 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.


Open in Devin Review

…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.

### Change

- `--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.

### Verification

- `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.

### 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 (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.
@0xghost42 0xghost42 requested a review from a team as a code owner May 14, 2026 11:04
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

7 Skipped Deployments
Project Deployment Actions Updated (UTC)
api-reference Skipped Skipped May 15, 2026 8:54am
component-library Skipped Skipped May 15, 2026 8:54am
developer-hub Skipped Skipped May 15, 2026 8:54am
entropy-explorer Skipped Skipped May 15, 2026 8:54am
insights Skipped Skipped May 15, 2026 8:54am
proposals Skipped Skipped May 15, 2026 8:54am
staking Skipped Skipped May 15, 2026 8:54am

Request Review

@vercel vercel Bot temporarily deployed to Preview – api-reference May 14, 2026 11:04 Inactive
@vercel vercel Bot temporarily deployed to Preview – staking May 14, 2026 11:04 Inactive
@vercel vercel Bot temporarily deployed to Preview – developer-hub May 14, 2026 11:04 Inactive
@vercel vercel Bot temporarily deployed to Preview – entropy-explorer May 14, 2026 11:04 Inactive
@vercel vercel Bot temporarily deployed to Preview – insights May 14, 2026 11:04 Inactive
@vercel vercel Bot temporarily deployed to Preview – component-library May 14, 2026 11:04 Inactive
@vercel vercel Bot temporarily deployed to Preview – proposals May 14, 2026 11:04 Inactive
devin-ai-integration[bot]

This comment was marked as resolved.

@vercel vercel Bot temporarily deployed to Preview – staking May 15, 2026 06:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – component-library May 15, 2026 06:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – api-reference May 15, 2026 06:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – developer-hub May 15, 2026 06:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – insights May 15, 2026 06:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – entropy-explorer May 15, 2026 06:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – proposals May 15, 2026 06:10 Inactive
@0xghost42
Copy link
Copy Markdown
Author

Pushed 4967b82 bumping @pythnetwork/price-pusher from 10.4.0 -> 10.5.0 per the version-bump finding.

Heads-up on coordination: #3689 (mnemonic env var) also bumps to 10.5.0, so whichever PR lands second will need a trivial rebase to 10.6.0. Both are minor backward-compatible additions, so the bump magnitude is the same regardless of order.

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.

devin-ai-integration[bot]

This comment was marked as resolved.

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.
@vercel vercel Bot temporarily deployed to Preview – api-reference May 15, 2026 08:54 Inactive
@vercel vercel Bot temporarily deployed to Preview – component-library May 15, 2026 08:54 Inactive
@vercel vercel Bot temporarily deployed to Preview – developer-hub May 15, 2026 08:54 Inactive
@vercel vercel Bot temporarily deployed to Preview – insights May 15, 2026 08:54 Inactive
@vercel vercel Bot temporarily deployed to Preview – entropy-explorer May 15, 2026 08:54 Inactive
@vercel vercel Bot temporarily deployed to Preview – proposals May 15, 2026 08:54 Inactive
@0xghost42
Copy link
Copy Markdown
Author

Pushed 342273d adding the missing export entry to apps/price_pusher/package.json:

"./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 apps/price_pusher/src/evm/* (7 entries), injective/command, injective/injective, near/command, etc. The CJS build would have generated dist/injective/endpoint-pool.cjs but dist/injective/injective.cjs would have failed at runtime trying to require('./endpoint-pool.js') without the export gate.

Lockfile / dependencies untouched.

@vercel vercel Bot temporarily deployed to Preview – staking May 15, 2026 08:54 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] price-pusher should accept an array of gRPC endpoints

1 participant