Repository navigation
feat(rpc): add remove-network-rpc script for RpcEndpoints (EXSC-1216) #2560
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
gvladika
merged 2 commits into
main
from
feature/exsc-1216-clean-up-dead-slow-and-misconfigured-rpc-endpoints-in
Oct 15, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| /** | ||
| * Remove one RPC endpoint from a chain in the `RpcEndpoints` collection, named by its host so the | ||
| * operator never pastes a URL that carries an API key. Use it to drop dead or misconfigured | ||
| * endpoints; run `bun fetch-rpcs` afterwards to regenerate the env file. | ||
| * | ||
| * Dry run unless `--apply` is passed. A host stored more than once on the chain is refused rather | ||
| * than guessed at. | ||
| */ | ||
| import 'dotenv/config' | ||
| import { defineCommand, runMain } from 'citty' | ||
| import { consola } from 'consola' | ||
| import { MongoClient } from 'mongodb' | ||
|
|
||
| import { mongoEq } from '../deploy/shared/mongo-log-utils' | ||
|
|
||
| import { | ||
| findEndpointsByHost, | ||
| hasApiCredentials, | ||
| hostOf, | ||
| selectEndpoints, | ||
| type IRpcEndpoint, | ||
| } from './rpcEndpoints' | ||
|
|
||
| const main = defineCommand({ | ||
| meta: { | ||
| name: 'remove-network-rpc', | ||
| description: 'Remove an RPC endpoint from the blockchain configuration', | ||
| }, | ||
| args: { | ||
| network: { | ||
| type: 'string', | ||
| description: 'Network name the endpoint is stored under', | ||
| required: true, | ||
| }, | ||
| host: { | ||
| type: 'string', | ||
| description: | ||
| 'Host of the endpoint to remove, as audit-rpc-priorities prints it', | ||
| required: true, | ||
| }, | ||
| environment: { | ||
| type: 'string', | ||
| description: 'Environment of the endpoint (default is production)', | ||
| required: false, | ||
| default: 'production', | ||
| }, | ||
| apply: { | ||
| type: 'boolean', | ||
| description: 'Write the removal to MongoDB (default is a dry run)', | ||
| required: false, | ||
| default: false, | ||
| }, | ||
| }, | ||
| async run({ args }) { | ||
| const { network, host, environment } = args | ||
|
|
||
| const MONGODB_URI = process.env.MONGODB_URI | ||
| if (!MONGODB_URI) { | ||
| consola.error('MONGODB_URI is not defined in the environment') | ||
| process.exit(1) | ||
| } | ||
|
|
||
| const client = new MongoClient(MONGODB_URI) | ||
| let exitCode = 0 | ||
| try { | ||
| await client.connect() | ||
| const collection = client | ||
| .db('blockchain-configs') | ||
| .collection('RpcEndpoints') | ||
|
|
||
| const doc = await collection.findOne({ chainName: mongoEq(network) }) | ||
| if (!doc) throw new Error(`No RpcEndpoints document for "${network}"`) | ||
| const rpcs: IRpcEndpoint[] = Array.isArray(doc.rpcs) ? doc.rpcs : [] | ||
|
|
||
| const matches = findEndpointsByHost(rpcs, host, environment) | ||
| if (matches.length === 0) | ||
| throw new Error( | ||
| `${network} has no ${environment} endpoint on host "${host}". Run audit-rpc-priorities to list its hosts.` | ||
| ) | ||
| if (matches.length > 1) | ||
| throw new Error( | ||
| `${network} has ${matches.length} ${environment} endpoints on host "${host}"; refusing to guess which one to remove` | ||
| ) | ||
|
|
||
| const [target] = matches as [IRpcEndpoint] | ||
| const remaining = selectEndpoints( | ||
| rpcs.filter((rpc) => rpc !== target), | ||
| environment | ||
| ) | ||
| if (remaining.length === 0) | ||
| throw new Error( | ||
| `Removing ${host} would leave ${network} with no ${environment} endpoint; add a replacement first` | ||
| ) | ||
|
|
||
| const label = `${network} [${environment}] ${hostOf(target.url)} p=${ | ||
| target.priority | ||
| } (${hasApiCredentials(target.url) ? 'keyed' : 'no key'})` | ||
|
|
||
| if (!args.apply) { | ||
| consola.info(`Dry run — would remove ${label}`) | ||
| consola.info( | ||
| `Resulting order: ${remaining | ||
| .map((rpc) => hostOf(rpc.url)) | ||
| .join(', ')}` | ||
| ) | ||
| return | ||
| } | ||
|
|
||
| // Endpoints predating the environment field count as production, so a production removal | ||
| // must also match a record with no environment, and must not touch the same URL stored for | ||
| // another environment. | ||
| const environmentMatch = | ||
| environment === 'production' | ||
| ? { $in: ['production', null] } | ||
| : mongoEq(environment) | ||
| const result = await collection.updateOne( | ||
| { chainName: mongoEq(network) }, | ||
|
gvladika marked this conversation as resolved.
|
||
| { | ||
| $set: { lastUpdated: new Date() }, | ||
| $pull: { | ||
| rpcs: { url: mongoEq(target.url), environment: environmentMatch }, | ||
| } as never, | ||
| } | ||
| ) | ||
| if (result.modifiedCount !== 1) | ||
| throw new Error(`MongoDB reported no change while removing ${label}`) | ||
|
|
||
| consola.success(`Removed ${label}`) | ||
| consola.info( | ||
| `Resulting order: ${remaining.map((rpc) => hostOf(rpc.url)).join(', ')}` | ||
| ) | ||
| } catch (error) { | ||
| consola.error(error instanceof Error ? error.message : error) | ||
| exitCode = 1 | ||
| } finally { | ||
| await client.close() | ||
| } | ||
| if (exitCode !== 0) process.exit(exitCode) | ||
| }, | ||
| }) | ||
|
|
||
| runMain(main) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.