Skip to content

feat: add OpenRPC JSON updater tool (#1837) #3810

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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fae7e1f
feat: add OpenRPC JSON updater tool (#1837)
mwb-al Jun 3, 2025
8bd6212
chore: update OpenRPC paths, enhance README, and pin GitHub Actions v…
mwb-al Jun 3, 2025
2a66691
chore: refactor utilities with new helper classes (#1837)
mwb-al Jun 3, 2025
1f0eb3b
chore: clean up code formatting and reorder imports in OpenRPC JSON u…
mwb-al Jun 4, 2025
b686d83
chore: clean up code formatting and reorder imports in OpenRPC JSON u…
mwb-al Jun 4, 2025
6d0cd1b
chore: add SPDX license identifiers and fix minor formatting issues i…
mwb-al Jun 4, 2025
c102620
chore: pin peter-evans/create-pull-request action to v7.0.8 in OpenRP…
mwb-al Jun 4, 2025
d9f620c
chore: remove npm cache configuration from OpenRPC JSON updater workf…
mwb-al Jun 4, 2025
b64ee28
chore: enhance OpenRPC JSON updater with configuration updates, impro…
mwb-al Jun 6, 2025
eabc938
chore: update OpenRPC JSON updater README with file creation details …
mwb-al Jun 6, 2025
91eb85b
chore: clean up formatting (#1837)
mwb-al Jun 6, 2025
c428955
chore: restrict OpenRPC updater workflow triggers to `docs/openrpc.js…
mwb-al Jun 9, 2025
56f3a8c
chore: remove `customFields` handling from OpenRPC JSON updater scrip…
mwb-al Jun 13, 2025
567c7dd
chore: update OpenRPC JSON updater config by pruning unused fields an…
mwb-al Jun 13, 2025
1de58c1
chore: enhance OpenRPC JSON updater with refined method handling and …
mwb-al Jun 13, 2025
487d0b0
Merge branch 'main' into 1837_openrpc-updater
mwb-al Jun 16, 2025
5937d64
Merge branch 'main' into 1837_openrpc-updater
mwb-al Jun 17, 2025
2a7a15d
chore: update OpenRPC JSON updater config to include `eth_getStorageA…
mwb-al Jun 17, 2025
d83ba75
Merge branch 'main' into 1837_openrpc-updater
Ferparishuertas Jun 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions .github/workflows/openrpc-updater.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@

name: OpenRPC JSON Updater

on:
push:
branches:
- main
paths:
- 'docs/openrpc.json'

jobs:
clone-and-build-execution-apis:
runs-on: ubuntu-latest

steps:
- name: Checkout execution-apis repo
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
ref: main
repository: 'ethereum/execution-apis'
path: 'execution-apis'

- name: Use Node.js TLS 20
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 20

- name: Install dependencies
run: npm install
working-directory: ./execution-apis

- name: Build project
run: npm run build
working-directory: ./execution-apis

- name: Upload openrpc.json as an artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: openrpc
path: ./execution-apis/refs-openrpc.json

update-openrpc:
runs-on: ubuntu-latest
needs: clone-and-build-execution-apis
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
ref: 'main'
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}

- name: Download openrpc.json artifact
uses: actions/download-artifact@v4
with:
name: openrpc
path: ./downloaded-artifacts/

- name: Copy generated openrpc.json to scripts directory
run: |
mkdir -p scripts/openrpc-json-updater
cp ./downloaded-artifacts/refs-openrpc.json scripts/openrpc-json-updater/original-openrpc.json

- name: Setup Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: '22'

- name: Install dependencies
run: |
cd scripts/openrpc-json-updater
npm install

- name: Generate comparison report
id: generate-report
run: |
cd scripts/openrpc-json-updater
REPORT_OUTPUT=$(node cli.js)
echo "REPORT_OUTPUT<<EOF" >> $GITHUB_ENV
echo "$REPORT_OUTPUT" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV

# This workflow automatically creates PRs when the OpenRPC JSON file differs from the upstream source.
# PRs are only created when actual changes are detected (SKIP_PR=false), ensuring that
# maintainers can review and approve schema updates before they're merged into the main branch.
# This provides a safety mechanism for tracking OpenRPC specification changes over time.
- name: Perform merge
id: merge
run: |
cd scripts/openrpc-json-updater
MERGE_OUTPUT=$(node cli.js --merge)
MERGE_EXIT_CODE=$?
echo "$MERGE_OUTPUT"

if [ $MERGE_EXIT_CODE -eq 0 ]; then
if [[ "$MERGE_OUTPUT" =~ No\ differences\ found\ after\ merge ]]; then
echo "No differences found. Skipping PR creation."
echo "SKIP_PR=true" >> $GITHUB_ENV
exit 0
elif [[ "$MERGE_OUTPUT" == *"Merge completed"* ]]; then
echo "Successfully updated openrpc.json"
echo "SKIP_PR=false" >> $GITHUB_ENV
else
echo "Unexpected output. Output was: $MERGE_OUTPUT"
exit 1
fi
else
echo "Failed to update file. Output was: $MERGE_OUTPUT"
exit 1
fi

- name: Generate unique branch name
id: branch-name
run: |
TIMESTAMP=$(date +%Y%m%d%H%M%S)
UNIQUE_BRANCH="update-openrpc-${TIMESTAMP}"
echo "UNIQUE_BRANCH=${UNIQUE_BRANCH}" >> $GITHUB_ENV
echo "Generated unique branch name: ${UNIQUE_BRANCH}"

- name: Create Pull Request
if: env.SKIP_PR != 'true'
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
commit-message: Update OpenRPC JSON
title: 'Update OpenRPC JSON'
body: |
# OpenRPC JSON Update

This PR updates the OpenRPC JSON file with the latest changes.

## Comparison Report
```
${{ env.REPORT_OUTPUT }}
```
branch: ${{ env.UNIQUE_BRANCH }}
base: 'main'
add-paths: docs/openrpc.json
delete-branch: true
91 changes: 91 additions & 0 deletions scripts/openrpc-json-updater/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# OpenRPC Diff & Merge CLI

A command-line tool for comparing and merging OpenRPC JSON specifications.

## GitHub Actions Integration

This tool is used with the `openrpc-updater.yml` workflow that automatically:

- Fetches the latest OpenRPC spec from the ethereum/execution-apis repository
- Compares it with our local version
- Creates a PR with the changes if differences are found

The GitHub Actions workflow requires a `PERSONAL_ACCESS_TOKEN` secret to be configured in your repository settings.
Add the token as a repository secret:

- Go to your repository → Settings → Secrets and variables → Actions
- Click "New repository secret"
- Name: `PERSONAL_ACCESS_TOKEN`
- Value: Your generated token

## Exclusions and Customizations (config.js)

This tool applies several filters when processing OpenRPC specifications to align them with our implementation
requirements:

### Skipped Methods

**Discarded Methods:**

- `engine_*` - Engine API methods are not supported in our implementation

**Not Implemented Methods:**

- `debug_getBadBlocks`
- `debug_getRawBlock`
- `debug_getRawHeader`
- `debug_getRawReceipts`
- `debug_getRawTransaction`

These debug methods are excluded because they are not implemented in our current system.

### Skipped Fields/Keys

The following fields are excluded from all methods:

- `examples` - Example values are omitted to reduce specification size
- `baseFeePerBlobGas` - Blob-related fields not applicable to our current implementation
- `blobGasUsedRatio` - Blob-related fields not applicable to our current implementation

### Custom Field Overrides

Certain fields are customized with our own descriptions and metadata for better integration with our system. This
includes custom summaries, descriptions, and schema titles for various `eth_*` methods like `eth_feeHistory`,
`eth_gasPrice`, `eth_getBalance`, etc.

These customizations ensure the OpenRPC specification aligns with our implementation's specific requirements and
documentation standards.

## Install

```shell script
npm install
```

## Usage

```shell script
node cli.js [options]

Options:
-g, --merge merge original -> modified (writes a new dated file)
```

### Examples

Full diff report:

```shell script
node cli.js
```

Merge changes:

```shell script
node cli.js --merge
```

## Important Note

The script creates a file in the `docs/openrpc.json` path, so the application must be run with appropriate file system
permissions.
56 changes: 56 additions & 0 deletions scripts/openrpc-json-updater/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0

import { mergeDocuments } from './operations/merge.js';
import { compareIgnoringFormatting, prepareDocuments } from './operations/prepare.js';
import { generateReport } from './operations/report.js';
import { readJson, writeJson } from './utils/file.utils.js';

const originalFilePath = './original-openrpc.json';
const modifiedFilePath = '../../docs/openrpc.json';

const { data: originalJson } = readJson(originalFilePath);
const { data: modifiedJson, originalContent: modifiedContent } = readJson(modifiedFilePath);

function parseArgs() {
const argv = process.argv.slice(2);
const result = { mergeFlag: false };

for (let i = 0; i < argv.length; i++) {
switch (argv[i]) {
case '-g':
case '--merge':
result.mergeFlag = true;
break;
}
}
return result;
}

function hasDifferences(original, merged) {
const differences = compareIgnoringFormatting(original, merged);
return differences && differences.length > 0;
}

(async () => {
const { mergeFlag } = parseArgs();

const { normalizedOriginal, normalizedModified } = prepareDocuments(originalJson, modifiedJson);

if (mergeFlag) {
const merged = mergeDocuments(normalizedOriginal, normalizedModified);

if (!hasDifferences(normalizedModified, merged)) {
console.log(`\nNo differences found after merge. No changes needed.\n`);
process.exit(0);
}

writeJson(modifiedFilePath, merged, modifiedContent);
console.log(`\nMerge completed. Updated file: '${modifiedFilePath}'.\n`);
return;
}

await generateReport(normalizedOriginal, normalizedModified).catch((err) => {
console.error('Unexpected error while generating report:', err);
process.exit(1);
});
})();
97 changes: 97 additions & 0 deletions scripts/openrpc-json-updater/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: Apache-2.0

export const SKIPPED_KEYS = ['examples', 'baseFeePerBlobGas', 'blobGasUsedRatio'];

export const CUSTOM_FIELDS = [
'eth_feeHistory.summary',
'eth_feeHistory.description',
'eth_feeHistory.params.2.description',
'eth_feeHistory.result.schema.properties.gasUsedRatio.description',
'eth_feeHistory.result.schema.properties.baseFeePerGas.title',
'eth_feeHistory.result.schema.properties.baseFeePerGas.description',
'eth_feeHistory.result.schema.properties.reward.title',
'eth_getTransactionCount.summary',
'eth_maxPriorityFeePerGas.result.schema.description',
'eth_getStorageAt.params.2.required',
];

export const DISCARDED_METHODS = ['engine_*'];

export const NOT_IMPLEMENTED_METHODS = [
'debug_getBadBlocks',
'debug_getRawBlock',
'debug_getRawHeader',
'debug_getRawReceipts',
'debug_getRawTransaction',
'eth_coinbase',
'eth_blobBaseFee',
'eth_syncing',
'eth_getProof',
];

export const SKIPPED_METHODS = [...DISCARDED_METHODS, ...NOT_IMPLEMENTED_METHODS];

export function shouldSkipMethod(methodName, path) {
if (!methodName) return false;

if (path) {
const fullPath = `${methodName}.${path}`;
if (CUSTOM_FIELDS.includes(fullPath)) return true;
}

for (const pattern of SKIPPED_METHODS) {
if (pattern === methodName) return true;

if (pattern.endsWith('*')) {
const prefix = pattern.slice(0, -1);
if (methodName.startsWith(prefix)) return true;
}
}
return false;
}

export function shouldSkipKey(key) {
if (!key) return false;
for (const pattern of SKIPPED_KEYS) {
if (pattern === key) return true;
if (pattern.endsWith('*')) {
const prefix = pattern.slice(0, -1);
if (key.startsWith(prefix)) return true;
}
}
return false;
}

export function shouldSkipPath(path) {
if (!path) return false;
const parts = path.split('.');
for (const part of parts) {
if (shouldSkipKey(part)) return true;
}
return false;
}

export function getSkippedMethodCategory(methodName) {
if (!methodName) return null;

const matchesPattern = (pattern, method) => {
if (pattern === method) return true;

if (pattern.endsWith('*')) {
const prefix = pattern.slice(0, -1);
return method.startsWith(prefix);
}

return false;
};

if (DISCARDED_METHODS.some((pattern) => matchesPattern(pattern, methodName))) {
return 'discarded';
}

if (NOT_IMPLEMENTED_METHODS.some((pattern) => matchesPattern(pattern, methodName))) {
return 'not implemented';
}

return null;
}
Loading
Loading