From fae7e1fccf21ac308390cfe8b28bdb49b3a5fe6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Tue, 3 Jun 2025 09:43:04 +0200 Subject: [PATCH 01/17] feat: add OpenRPC JSON updater tool (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- .github/workflows/openrpc-updater.yml | 137 ++ scripts/openrpc-json-updater/README.md | 30 + scripts/openrpc-json-updater/cli.js | 54 + scripts/openrpc-json-updater/config.js | 135 + .../modified-openrpc.json | 2169 +++++++++++++++++ .../openrpc-json-updater/operations/merge.js | 244 ++ .../operations/prepare.js | 19 + .../openrpc-json-updater/operations/report.js | 64 + .../original-openrpc.json | 1 + .../openrpc-json-updater/package-lock.json | 18 + scripts/openrpc-json-updater/package.json | 6 + .../openrpc-json-updater/utils/file.utils.js | 75 + .../openrpc-json-updater/utils/merge.utils.js | 205 ++ .../utils/openrpc.utils.js | 162 ++ 14 files changed, 3319 insertions(+) create mode 100644 .github/workflows/openrpc-updater.yml create mode 100644 scripts/openrpc-json-updater/README.md create mode 100644 scripts/openrpc-json-updater/cli.js create mode 100644 scripts/openrpc-json-updater/config.js create mode 100644 scripts/openrpc-json-updater/modified-openrpc.json create mode 100644 scripts/openrpc-json-updater/operations/merge.js create mode 100644 scripts/openrpc-json-updater/operations/prepare.js create mode 100644 scripts/openrpc-json-updater/operations/report.js create mode 100644 scripts/openrpc-json-updater/original-openrpc.json create mode 100644 scripts/openrpc-json-updater/package-lock.json create mode 100644 scripts/openrpc-json-updater/package.json create mode 100644 scripts/openrpc-json-updater/utils/file.utils.js create mode 100644 scripts/openrpc-json-updater/utils/merge.utils.js create mode 100644 scripts/openrpc-json-updater/utils/openrpc.utils.js diff --git a/.github/workflows/openrpc-updater.yml b/.github/workflows/openrpc-updater.yml new file mode 100644 index 0000000000..be779d58a0 --- /dev/null +++ b/.github/workflows/openrpc-updater.yml @@ -0,0 +1,137 @@ + +name: OpenRPC JSON Updater + +on: + push: + branches: + - main + +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@v3 + with: + ref: 'main' + token: ${{ secrets.PAT_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@v3 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: 'scripts/openrpc-json-updater/package-lock.json' + + - 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<> $GITHUB_ENV + echo "$REPORT_OUTPUT" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - 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\.\ Updated\ file:\ \'([^\']+)\'\. ]]; then + echo "Successfully updated modified-openrpc.json" + echo "MERGE_FILE=modified-openrpc.json" >> $GITHUB_ENV + echo "MERGE_FILENAME=modified-openrpc.json" >> $GITHUB_ENV + 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="${{ github.event.inputs.branch_name }}-${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@v5 + with: + token: ${{ secrets.PAT_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: | + scripts/openrpc-json-updater/modified-openrpc.json + delete-branch: false \ No newline at end of file diff --git a/scripts/openrpc-json-updater/README.md b/scripts/openrpc-json-updater/README.md new file mode 100644 index 0000000000..276f4d4982 --- /dev/null +++ b/scripts/openrpc-json-updater/README.md @@ -0,0 +1,30 @@ +# OpenRPC Diff & Merge CLI + +## 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 +``` diff --git a/scripts/openrpc-json-updater/cli.js b/scripts/openrpc-json-updater/cli.js new file mode 100644 index 0000000000..ba6e9d47c6 --- /dev/null +++ b/scripts/openrpc-json-updater/cli.js @@ -0,0 +1,54 @@ +import { readJson, writeJson } from './utils/file.utils.js'; +import { mergeDocuments } from './operations/merge.js'; +import { generateReport } from './operations/report.js'; +import { prepareDocuments, compareIgnoringFormatting } from './operations/prepare.js'; + +const originalFilePath = './original-openrpc.json'; +const modifiedFilePath = './modified-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); + }); +})(); diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js new file mode 100644 index 0000000000..b97d47b739 --- /dev/null +++ b/scripts/openrpc-json-updater/config.js @@ -0,0 +1,135 @@ +export const SKIPPED_KEYS = [ + "examples", + "baseFeePerBlobGas", + "blobGasUsedRatio" +]; + +export const CUSTOM_FIELDS = [ + "eth_accounts.description", + "eth_accounts.result.description", + "eth_accounts.result.schema.title", + + "eth_call.summary", + + "eth_coinbase.summary", + "eth_blobBaseFee.summary", + + "eth_feeHistory.summary", + "eth_feeHistory.description", + "eth_feeHistory.params.2.description", + "eth_feeHistory.result.name", + "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_gasPrice.summary", + + "eth_getBalance.result.schema.title", + + "eth_getBlockTransactionCountByHash.result.name", + "eth_getBlockTransactionCountByNumber.result.name", + + "eth_getLogs.summary", + "eth_getStorageAt.summary", + "eth_getStorageAt.params.1.name", + "eth_getStorageAt.result.name", + + "eth_getTransactionCount.summary", + "eth_getTransactionCount.result.name", + "eth_getTransactionCount.result.schema.title", + + "eth_maxPriorityFeePerGas.summary", + "eth_maxPriorityFeePerGas.result.schema.description", + "eth_maxPriorityFeePerGas.result.schema.title", + + "eth_newBlockFilter.summary", + "eth_newBlockFilter.result.name", +]; + +export const DISCARDED_METHODS = [ + "engine_*" +]; + +export const NOT_IMPLEMENTED_METHODS = [ + "debug_getBadBlocks", + "debug_getRawBlock", + "debug_getRawHeader", + "debug_getRawReceipts", + "debug_getRawTransaction" +]; + +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; + + for (const pattern of DISCARDED_METHODS) { + if (pattern === methodName) return 'discarded'; + + if (pattern.endsWith('*')) { + const prefix = pattern.slice(0, -1); + if (methodName.startsWith(prefix)) return 'discarded'; + } + } + + for (const pattern of NOT_IMPLEMENTED_METHODS) { + if (pattern === methodName) return 'not implemented'; + + if (pattern.endsWith('*')) { + const prefix = pattern.slice(0, -1); + if (methodName.startsWith(prefix)) return 'not implemented'; + } + } + + return null; +} diff --git a/scripts/openrpc-json-updater/modified-openrpc.json b/scripts/openrpc-json-updater/modified-openrpc.json new file mode 100644 index 0000000000..c234eba6d9 --- /dev/null +++ b/scripts/openrpc-json-updater/modified-openrpc.json @@ -0,0 +1,2169 @@ +{ + "openrpc": "1.0.0", + "info": { + "title": "Hedera JSON-RPC Specification", + "description": "A specification of the implemented Ethereum JSON RPC APIs interface for Hedera clients and adheres to the Ethereum execution APIs schema.", + "version": "0.69.0-SNAPSHOT" + }, + "servers": [ + { + "name": "Mainnet", + "url": "https://mainnet.hashio.io/api", + "description": "Hedera Mainnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." + }, + { + "name": "Testnet", + "url": "https://testnet.hashio.io/api", + "description": "Hedera Testnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." + }, + { + "name": "Previewnet", + "url": "https://previewnet.hashio.io/api", + "description": "Hedera Previewnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." + }, + { + "name": "localhost", + "url": "http://localhost:7546", + "description": "Run your own instance of [`hiero-json-rpc-relay`](https://github.com/hiero-ledger/hiero-json-rpc-relay) on `localhost`, and configure it connect to your choice of Hedera networks. No rate limits apply." + }, + { + "name": "WS Mainnet", + "url": "wss://mainnet.hashio.io/ws", + "description": "Hedera Web Socket Mainnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." + }, + { + "name": "WS Testnet", + "url": "wss://testnet.hashio.io/ws", + "description": "Hedera Web Socket Testnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." + }, + { + "name": "WS Previewnet", + "url": "wss://previewnet.hashio.io/ws", + "description": "Hedera Web Socket Previewnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." + }, + { + "name": "WS localhost", + "url": "ws://localhost:8546", + "description": "Run your own instance of Web Socket [`hiero-json-rpc-relay`](https://github.com/hiero-ledger/hiero-json-rpc-relay) on `localhost`, and configure it connect to your choice of Hedera networks. No rate limits apply." + } + ], + "methods": [ + { + "name": "eth_accounts", + "summary": "Returns a list of addresses owned by client.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "Accounts", + "description": "Always returns an empty array", + "schema": { + "title": "empty array", + "type": "array", + "pattern": "^\\[\\s*\\]$" + } + } + }, + { + "name": "eth_blockNumber", + "summary": "Returns the number of most recent block.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [], + "result": { + "name": "Block number", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_call", + "summary": "Executes a new message call immediately without creating a transaction on the block chain. ", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/TransactionWithSender" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Return data", + "schema": { + "$ref": "#/components/schemas/bytes" + } + } + }, + { + "name": "eth_chainId", + "summary": "Returns the chain ID of the current network.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [], + "result": { + "name": "Chain ID", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_coinbase", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + }, + { + "name": "eth_blobBaseFee", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + }, + { + "name": "eth_estimateGas", + "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/TransactionWithSender" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Gas used", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_feeHistory", + "summary": "Returns transaction base fee per gas and effective priority fee per gas for the requested/supported block range.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "blockCount", + "description": "Requested range of blocks. Clients will return less than the requested range if not all blocks are available.", + "required": true, + "schema": { + "$ref": "#/components/schemas/uint" + } + }, + { + "name": "newestBlock", + "description": "Highest block of the requested range.", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + }, + { + "name": "rewardPercentiles", + "description": "A monotonically increasing list of percentile values. For each block in the requested range, the transactions will be sorted in ascending order by effective tip per gas and the coresponding effective tip for the percentile will be determined, accounting for gas consumed.", + "required": true, + "schema": { + "title": "rewardPercentiles", + "type": "array", + "items": { + "title": "rewardPercentile", + "description": "Floating point value between 0 and 100.", + "type": "number", + "pattern": "^[0-9][0-9]?$|^100$" + } + } + } + ], + "result": { + "name": "feeHistoryResult", + "description": "Fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available.", + "schema": { + "title": "feeHistoryResults", + "description": "Fee history results.", + "type": "object", + "required": ["oldestBlock", "baseFeePerGas", "gasUsedRatio"], + "properties": { + "oldestBlock": { + "title": "oldestBlock", + "description": "Lowest number block of returned range.", + "$ref": "#/components/schemas/uint" + }, + "gasUsedRatio": { + "title": "gasUsedRatio", + "description": "An array of gas used ratio.", + "type": "array", + "items": { + "title": "floating point number", + "type": "number", + "pattern": "^([0-9].[0-9]*|0)$" + } + }, + "baseFeePerGas": { + "title": "baseFeePerGas", + "description": "An array of block base fees per gas. This includes the next block after the newest of the returned range.", + "type": "array", + "items": { + "$ref": "#/components/schemas/uint" + } + }, + "reward": { + "title": "reward", + "description": "A two-dimensional array of effective priority fees per gas at the requested block percentiles.", + "type": "array", + "items": { + "title": "rewardPercentile", + "description": "An array of effective priority fee per gas data points from a single block. All zeroes are returned if the block is empty.", + "type": "array", + "items": { + "title": "rewardPercentile", + "description": "A given percentile sample of effective priority fees per gas from a single block in ascending order, weighted by gas used. Zeroes are returned if the block is empty.", + "$ref": "#/components/schemas/uint" + } + } + } + } + } + } + }, + { + "name": "eth_gasPrice", + "summary": "Returns the current price per gas in weibars.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [], + "result": { + "name": "Gas price", + "schema": { + "title": "Gas price", + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_getBalance", + "summary": "Returns the balance of the account of given address.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Balance", + "schema": { + "title": "hex encoded unsigned integer", + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_getBlockByHash", + "summary": "Returns information about a block by hash.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Block hash", + "required": true, + "schema": { + "$ref": "#/components/schemas/hash32" + } + }, + { + "name": "Hydrated transactions", + "required": true, + "schema": { + "title": "hydrated", + "type": "boolean" + } + } + ], + "result": { + "name": "Block information", + "schema": { + "$ref": "#/components/schemas/Block" + } + } + }, + { + "name": "eth_getBlockByNumber", + "summary": "Returns information about a block by number.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + }, + { + "name": "Hydrated transactions", + "required": true, + "schema": { + "title": "hydrated", + "type": "boolean" + } + } + ], + "result": { + "name": "Block information", + "schema": { + "$ref": "#/components/schemas/Block" + } + } + }, + { + "name": "eth_getBlockTransactionCountByHash", + "summary": "Returns the number of transactions in a block from a block matching the given block hash.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "Block hash", + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + ], + "result": { + "name": "Transactions count", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_getBlockTransactionCountByNumber", + "summary": "Returns the number of transactions in a block matching the given block number.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "Block", + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Transactions count", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_getCode", + "summary": "Returns code at a given address.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Bytecode", + "schema": { + "$ref": "#/components/schemas/bytes" + } + } + }, + { + "name": "eth_getLogs", + "summary": "Returns an array of all logs matching a given filter object.", + "description": "The block range filter, _i.e._, `fromBlock` and `toBlock` arguments, cannot be larger than `ETH_GET_LOGS_BLOCK_RANGE_LIMIT` (defaults to `1000`). However, when `address` represents a single address, either a `string` or an array with a single element, this restriction is lifted. In any case, if the `topics` param is present the block range must be within ~`302,400` blocks, the equivalent to 7 days.\n\nWhen the logs for an individual address exceeds `MIRROR_NODE_CONTRACT_RESULTS_LOGS_PG_MAX * MIRROR_NODE_LIMIT_PARAM` (defaults to `20k`) an error `-32011` _Mirror Node pagination count range too large_ is returned. These settings are the Mirror Node page limit (defaults to `200`) and Mirror Node entries per page (defaults to `100`) respectively. ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Filter", + "schema": { + "$ref": "#/components/schemas/Filter" + } + } + ], + "result": { + "name": "Log objects", + "schema": { + "$ref": "#/components/schemas/FilterResults" + } + } + }, + { + "name": "eth_getStorageAt", + "summary": "Returns the value from a storage position at a given address and block. The optional block param may be latest or a valid tag of a historical block.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "Position", + "required": true, + "schema": { + "$ref": "#/components/schemas/uint" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTagOrHash" + } + } + ], + "result": { + "name": "Value from storage", + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + }, + { + "name": "eth_getTransactionByBlockHashAndIndex", + "summary": "Returns information about a transaction by block hash and transaction index position.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "Block hash", + "required": true, + "schema": { + "$ref": "#/components/schemas/hash32" + } + }, + { + "name": "Transaction index", + "required": true, + "schema": { + "$ref": "#/components/schemas/uint" + } + } + ], + "result": { + "name": "Transaction information", + "schema": { + "$ref": "#/components/schemas/TransactionInfo" + } + } + }, + { + "name": "eth_getTransactionByBlockNumberAndIndex", + "summary": "Returns information about a transaction by block number and transaction index position.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + }, + { + "name": "Transaction index", + "required": true, + "schema": { + "$ref": "#/components/schemas/uint" + } + } + ], + "result": { + "name": "Transaction information", + "schema": { + "$ref": "#/components/schemas/TransactionInfo" + } + } + }, + { + "name": "eth_getTransactionByHash", + "summary": "Returns the information about a transaction requested by transaction hash.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Transaction hash", + "required": true, + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + ], + "result": { + "name": "Transaction information", + "schema": { + "$ref": "#/components/schemas/TransactionInfo" + } + } + }, + { + "name": "eth_getTransactionCount", + "summary": "Returns the number of transactions sent from an address.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Transaction count", + "schema": { + "title": "Transaction count", + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_getTransactionReceipt", + "summary": "Returns the receipt of a transaction by transaction hash.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Transaction hash", + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + ], + "result": { + "name": "Receipt Information", + "schema": { + "$ref": "#/components/schemas/ReceiptInfo" + } + } + }, + { + "name": "eth_getUncleByBlockHashAndIndex", + "summary": "Returns information about a uncle of a block by hash and uncle index position.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "eth_getUncleByBlockHashAndIndex result", + "schema": { + "description": "Always returns null. There are no uncles in Hedera.", + "$ref": "#/components/schemas/null" + } + } + }, + { + "name": "eth_getUncleByBlockNumberAndIndex", + "summary": "Returns information about a uncle of a block by number and uncle index position.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "eth_getUncleByBlockNumberAndIndex result", + "schema": { + "description": "Always returns null. There are no uncles in Hedera.", + "$ref": "#/components/schemas/null" + } + } + }, + { + "name": "eth_getUncleCountByBlockHash", + "summary": "Returns the number of uncles in a block from a block matching the given block hash.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "eth_getUncleCountByBlockHash result", + "schema": { + "description": "Always returns '0x0'. There are no uncles in Hedera.", + "title": "hex encoded unsigned integer", + "type": "string", + "pattern": "0x0" + } + } + }, + { + "name": "eth_getUncleCountByBlockNumber", + "summary": "Returns the number of transactions in a block matching the given block number.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "eth_getUncleCountByBlockNumber result", + "schema": { + "description": "Always returns '0x0'. There are no uncles in Hedera.", + "title": "hex encoded unsigned integer", + "type": "string", + "pattern": "0x0" + } + } + }, + { + "name": "eth_getWork", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + }, + { + "name": "eth_hashrate", + "summary": "Returns the number of hashes per second that the node is mining with. Always returns 0x0.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "Mining status", + "schema": { + "title": "Hashrate", + "type": "string", + "pattern": "0x0" + } + } + }, + { + "name": "eth_maxPriorityFeePerGas", + "summary": "Returns a fee per gas that is an estimate of how much you can pay as a priority fee, or 'tip', to get a transaction included in the current block.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [], + "result": { + "name": "eth_maxPriorityFeePerGas result", + "schema": { + "description": "Always returns '0x0'. Hedera doesn't have a concept of tipping nodes to promote any behavior", + "title": "hex encoded unsigned integer", + "type": "string", + "pattern": "0x0" + } + } + }, + { + "name": "eth_mining", + "summary": "Returns whether the client is a miner. We don't mine, so this always returns false.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "Mining status", + "schema": { + "title": "miningStatus", + "type": "boolean" + } + } + }, + { + "name": "eth_newBlockFilter", + "summary": "Creates a filter object, to notify of newly created blocks.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "Filter ID", + "schema": { + "$ref": "#/components/schemas/hash16" + } + }, + "tags": [ + { + "name": "alpha API" + } + ] + }, + { + "name": "eth_newFilter", + "summary": "Creates a filter object, based on filter options, to notify when the state changes (logs).", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "LogFilter", + "schema": { + "$ref": "#/components/schemas/LogFilter" + } + } + ], + "result": { + "name": "Filter ID", + "schema": { + "$ref": "#/components/schemas/hash16" + } + }, + "tags": [ + { + "name": "alpha API" + } + ] + }, + { + "name": "eth_uninstallFilter", + "summary": "Uninstalls a filter with given id.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "FilterID", + "schema": { + "$ref": "#/components/schemas/hash16" + } + } + ], + "result": { + "name": "Uninstall status", + "schema": { + "type": "boolean" + } + }, + "tags": [ + { + "name": "alpha API" + } + ] + }, + { + "name": "eth_newPendingTransactionFilter", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + }, + "tags": [ + { + "name": "alpha API" + } + ] + }, + { + "name": "eth_getFilterLogs", + "summary": "Returns an array of all logs matching filter with given id.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "Filter ID", + "schema": { + "$ref": "#/components/schemas/hash16" + } + } + ], + "result": { + "name": "Filter result", + "schema": { + "$ref": "#/components/schemas/FilterResults" + } + }, + "tags": [ + { + "name": "alpha API" + } + ] + }, + { + "name": "eth_getFilterChanges", + "summary": "Gets the latest results since the last request for a provided filter according to its type.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "Filter ID", + "schema": { + "$ref": "#/components/schemas/hash16" + } + } + ], + "result": { + "name": "Filter result", + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "blockHash": { + "title": "block hash", + "$ref": "#/components/schemas/hash32" + } + } + }, + { + "name": "Log objects", + "schema": { + "$ref": "#/components/schemas/FilterResults" + } + } + ] + } + }, + "tags": [ + { + "name": "alpha API" + } + ] + }, + { + "name": "eth_protocolVersion", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + }, + { + "name": "eth_sendRawTransaction", + "summary": "Submits a raw transaction.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/bytes" + } + } + ], + "result": { + "name": "Transaction hash", + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + }, + { + "name": "eth_sendTransaction", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + }, + { + "name": "eth_signTransaction", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + }, + { + "name": "eth_sign", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + }, + { + "name": "eth_submitHashrate", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + }, + { + "name": "eth_submitWork", + "summary": "Used for submitting a proof-of-work solution.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "PoW solution", + "schema": { + "title": "No proof-of-work", + "description": "Always returns false.", + "type": "boolean" + } + } + }, + { + "name": "eth_syncing", + "summary": "Returns syncing status.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "Syncing status", + "schema": { + "title": "Not syncing", + "description": "Always returns false.", + "type": "boolean" + } + } + }, + { + "name": "net_listening", + "summary": "Returns true if client is actively listening for network connections.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "Listening status.", + "schema": { + "title": "Not listening", + "description": "Always returns false.", + "type": "boolean" + } + } + }, + { + "name": "net_version", + "summary": "Returns the current chain id.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [], + "result": { + "name": "The current chain id.", + "schema": { + "$ref": "#/components/schemas/int" + } + } + }, + { + "name": "net_peerCount", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + }, + { + "name": "web3_clientVersion", + "summary": "Returns the current client version.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [], + "result": { + "name": "The current client version.", + "schema": { + "type": "string", + "pattern": "relay/[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9-]+)?$" + } + } + }, + { + "name": "web3_sha3", + "summary": "Returns sha3 of the input.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [], + "result": { + "name": "The sha3 result.", + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + }, + { + "name": "eth_subscribe", + "summary": "Creates a new subscription for desired events. Sends data as soon as it occurs.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "subscription_type", + "required": true, + "schema": { + "type": "string", + "enum": ["logs"] + }, + "description": "The type of event you want to subscribe to." + }, + { + "name": "options", + "required": false, + "schema": { + "type": "object", + "properties": { + "address": { + "oneOf": [ + { + "$ref": "#/components/schemas/address" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/address" + } + } + ], + "description": "Single address or array of addresses to restrict the logs." + }, + "topics": { + "$ref": "#/components/schemas/FilterTopics", + "description": "Array of topics used to filter logs." + } + }, + "additionalProperties": false + }, + "description": "Filter options for the subscription. Required for 'logs' type." + } + ], + "result": { + "name": "subscription_id", + "schema": { + "type": "string", + "$ref": "#/components/schemas/hash32" + }, + "description": "The hex encoded subscription ID used to identify and manage the subscription." + } + }, + { + "name": "eth_unsubscribe", + "summary": "Cancels a subscription.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", + "params": [ + { + "name": "subscription_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The subscription ID to cancel." + } + ], + "result": { + "name": "success", + "schema": { + "type": "boolean" + }, + "description": "True if the subscription was successfully cancelled, otherwise false." + } + }, + { + "name": "debug_traceTransaction", + "summary": "Attempts to run the transaction in the exact same manner as it was executed on the network.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "transactionHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/hash32" + }, + "description": "The hash of the transaction to trace." + }, + { + "name": "tracer", + "required": false, + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/TracerType" + }, + { + "$ref": "#/components/schemas/TracerConfig" + }, + { + "$ref": "#/components/schemas/TracerConfigWrapper" + } + ] + }, + "description": "Specifies the type of tracer or configuration object for the tracer." + }, + { + "name": "tracerConfig", + "required": false, + "schema": { + "$ref": "#/components/schemas/TracerConfig" + }, + "description": "Configuration object for the tracer." + } + ], + "result": { + "name": "trace", + "schema": { + "type": "object", + "properties": { + "callFrame": { + "$ref": "#/components/schemas/callframe" + } + }, + "required": ["callFrame"], + "additionalProperties": false + }, + "description": "The trace object containing detailed information about the transaction execution, encapsulated in a call frame." + } + }, + { + "name": "debug_traceBlockByNumber", + "summary": "Returns the tracing result for all transactions in the block specified by number with a tracer.", + "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", + "params": [ + { + "name": "blockNumber", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + }, + "description": "The block number or tag (e.g., 'latest', 'earliest', 'pending', 'safe', 'finalized')." + }, + { + "name": "tracerOptions", + "required": false, + "schema": { + "type": "object", + "properties": { + "tracer": { + "$ref": "#/components/schemas/BlockTracerType", + "description": "The type of tracer to use. Either 'callTracer' or 'prestateTracer'." + }, + "tracerConfig": { + "type": "object", + "description": "Configuration options for the specified tracer", + "properties": { + "onlyTopCall": { + "type": "boolean", + "description": "When set to true, this will only trace the primary (top-level) call and not any sub-calls." + } + } + } + } + }, + "description": "Specifies the type of tracer and optional configuration. Supported tracers are 'callTracer' and 'prestateTracer'." + } + ], + "result": { + "name": "traces", + "schema": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "title": "Call Tracer Response", + "description": "callTracer response format", + "properties": { + "type": { + "type": "string", + "enum": ["CALL", "CREATE"], + "description": "The type of action (CALL for function calls or CREATE for contract creation)." + }, + "from": { + "$ref": "#/components/schemas/address", + "description": "The address of the sender." + }, + "to": { + "$ref": "#/components/schemas/address", + "description": "The address of the receiver. In the case of a contract creation, this might be null." + }, + "value": { + "$ref": "#/components/schemas/uint", + "description": "The value transferred with the call, in hexadecimal." + }, + "gas": { + "$ref": "#/components/schemas/uint", + "description": "The gas provided for the call, in hexadecimal." + }, + "gasUsed": { + "$ref": "#/components/schemas/uint", + "description": "The gas used during the call, in hexadecimal." + }, + "input": { + "$ref": "#/components/schemas/bytes", + "description": "The input data sent with the call." + }, + "output": { + "$ref": "#/components/schemas/bytes", + "description": "The output data returned by the call." + }, + "error": { + "type": "string", + "description": "Error encountered during the call, if any." + }, + "revertReason": { + "type": "string", + "description": "Solidity revert reason, if applicable." + }, + "calls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/subcall" + }, + "description": "Sub-calls made during this call frame." + } + } + }, + { + "type": "object", + "title": "Prestate Tracer Response", + "description": "prestateTracer response format - an object where keys are ethereum addresses and values contain account state data", + "properties": { + "balance": { + "$ref": "#/components/schemas/uint", + "description": "The balance of the account/contract, expressed in wei and encoded as a hexadecimal string" + }, + "nonce": { + "$ref": "#/components/schemas/uint", + "description": "The latest nonce of the account, represented as an unsigned integer. Historical nonces are not supported by now." + }, + "code": { + "$ref": "#/components/schemas/bytes", + "description": "The bytecode of the contract, encoded as a hexadecimal string. If the account is not a contract, this will be an empty hex string (0x)." + }, + "storage": { + "type": "object", + "description": "A map of key-value pairs representing the storage slots of the contract. The keys and values are both encoded as hexadecimal strings. If the account is not a contract, this will be an empty object ({}).", + "additionalProperties": { + "$ref": "#/components/schemas/bytes32" + } + } + } + } + ] + } + }, + "description": "An array of trace objects containing detailed information about all transactions in the block. For callTracer, each item is a call frame with transaction details. For prestateTracer, each item is an object mapping addresses to their account state." + } + }, + { + "name": "eth_getProof", + "summary": "Always returns UNSUPPORTED_METHOD error.", + "params": [], + "result": { + "$ref": "#/components/schemas/unsupportedError" + } + } + ], + "components": { + "schemas": { + "address": { + "title": "hex encoded address", + "type": "string", + "pattern": "^0x[0-9,a-f,A-F]{40}$" + }, + "addresses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/address" + } + }, + "byte": { + "title": "hex encoded byte", + "type": "string", + "pattern": "^0x([0-9,a-f,A-F]?){1,2}$" + }, + "bytes": { + "title": "hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]*$" + }, + "bytes8": { + "title": "8 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{16}$" + }, + "bytes32": { + "title": "32 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "bytes256": { + "title": "256 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{512}$" + }, + "bytes65": { + "title": "65 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{512}$" + }, + "int": { + "title": "decimal value integer", + "type": "string", + "pattern": "^\\d+$" + }, + "uint": { + "title": "hex encoded unsigned integer", + "type": "string", + "pattern": "^0x([1-9a-f]+[0-9a-f]*|0)$" + }, + "uint256": { + "title": "hex encoded unsigned integer", + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "hash16": { + "title": "16 byte hex value", + "type": "string", + "pattern": "^0x[0-9a-f]{32}$" + }, + "hash32": { + "title": "32 byte hex value", + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "null": { + "title": "null value", + "type": "null", + "pattern": "null" + }, + "subcall": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["CALL", "CREATE"], + "description": "The type of action (CALL for function calls or CREATE for contract creation)." + }, + "from": { + "$ref": "#/components/schemas/address", + "description": "The address of the sender." + }, + "to": { + "$ref": "#/components/schemas/address", + "description": "The address of the receiver. In the case of a contract creation, this might be null." + }, + "value": { + "$ref": "#/components/schemas/uint", + "description": "The value transferred with the call, in hexadecimal." + }, + "gas": { + "$ref": "#/components/schemas/uint", + "description": "The gas provided for the call, in hexadecimal." + }, + "gasUsed": { + "$ref": "#/components/schemas/uint", + "description": "The gas used during the call, in hexadecimal." + }, + "input": { + "$ref": "#/components/schemas/bytes", + "description": "The input data sent with the call." + }, + "output": { + "$ref": "#/components/schemas/bytes", + "description": "The output data returned by the call." + }, + "error": { + "type": "string", + "description": "Error encountered during the call, if any." + }, + "revertReason": { + "type": "string", + "description": "Solidity revert reason, if applicable." + } + }, + "required": ["from", "gas", "input"] + }, + "Block": { + "title": "Block object", + "type": "object", + "required": [ + "parentHash", + "sha3Uncles", + "miner", + "stateRoot", + "transactionsRoot", + "receiptsRoot", + "logsBloom", + "number", + "gasLimit", + "gasUsed", + "timestamp", + "extraData", + "mixHash", + "nonce", + "size", + "transactions", + "uncles", + "withdrawals", + "withdrawalsRoot" + ], + "properties": { + "parentHash": { + "title": "Parent block hash", + "$ref": "#/components/schemas/hash32" + }, + "sha3Uncles": { + "title": "Ommers hash", + "$ref": "#/components/schemas/hash32" + }, + "miner": { + "title": "Coinbase", + "$ref": "#/components/schemas/address" + }, + "stateRoot": { + "title": "State root", + "$ref": "#/components/schemas/hash32" + }, + "transactionsRoot": { + "title": "Transactions root", + "$ref": "#/components/schemas/hash32" + }, + "receiptsRoot": { + "title": "Receipts root", + "$ref": "#/components/schemas/hash32" + }, + "logsBloom": { + "title": "Bloom filter", + "$ref": "#/components/schemas/bytes256" + }, + "difficulty": { + "title": "Difficulty", + "$ref": "#/components/schemas/bytes" + }, + "number": { + "title": "Number", + "$ref": "#/components/schemas/uint" + }, + "gasLimit": { + "title": "Gas limit", + "$ref": "#/components/schemas/uint" + }, + "gasUsed": { + "title": "Gas used", + "$ref": "#/components/schemas/uint" + }, + "timestamp": { + "title": "Timestamp", + "$ref": "#/components/schemas/uint" + }, + "extraData": { + "title": "Extra data", + "$ref": "#/components/schemas/bytes" + }, + "mixHash": { + "title": "Mix hash", + "$ref": "#/components/schemas/hash32" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/bytes8" + }, + "totalDifficulty": { + "title": "Total difficult", + "$ref": "#/components/schemas/uint" + }, + "baseFeePerGas": { + "title": "Base fee per gas", + "$ref": "#/components/schemas/uint" + }, + "size": { + "title": "Block size", + "$ref": "#/components/schemas/uint" + }, + "transactions": { + "anyOf": [ + { + "title": "Transaction hashes", + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + }, + { + "title": "Full transactions", + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionSigned" + } + } + ] + }, + "uncles": { + "title": "Uncles", + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + }, + "withdrawals": { + "title": "Withdrawals", + "type": "array", + "default": [] + }, + "withdrawalsRoot": { + "title": "Withdrawals root", + "default": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + } + }, + "BlockTag": { + "type": "string", + "enum": ["earliest", "latest", "pending", "finalized", "safe"], + "description": "`earliest`: The lowest numbered block the client has available; `latest` | `pending` | 'finalized' | 'safe' : The most recent block." + }, + "BlockNumberOrTag": { + "title": "Block number or tag", + "oneOf": [ + { + "title": "Block number", + "$ref": "#/components/schemas/uint" + }, + { + "title": "Block tag", + "$ref": "#/components/schemas/BlockTag" + } + ] + }, + "BlockNumberOrTagOrHash": { + "title": "Block number or tag", + "oneOf": [ + { + "title": "Block number", + "$ref": "#/components/schemas/uint" + }, + { + "title": "Block tag", + "$ref": "#/components/schemas/BlockTag" + }, + { + "title": "Block hash", + "$ref": "#/components/schemas/hash32" + } + ] + }, + "CallTracerConfig": { + "title": "CallTracer config", + "type": "object", + "properties": { + "onlyTopCall": { + "type": "boolean" + } + } + }, + "OpcodeLoggerConfig": { + "title": "OpcodeLogger config", + "type": "object", + "properties": { + "enableMemory": { + "type": "boolean" + }, + "disableStack": { + "type": "boolean" + }, + "disableStorage": { + "type": "boolean" + } + } + }, + "TracerConfig": { + "title": "Tracer config", + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/CallTracerConfig" + }, + { + "$ref": "#/components/schemas/OpcodeLoggerConfig" + } + ] + }, + "TracerType": { + "title": "Tracer type", + "type": "string", + "enum": ["callTracer", "opcodeLogger"] + }, + "TracerConfigWrapper": { + "title": "Tracer config wrapper", + "type": "object", + "properties": { + "tracer": { + "$ref": "#/components/schemas/TracerType" + }, + "tracerConfig": { + "$ref": "#/components/schemas/TracerConfig" + } + } + }, + "BlockTracerType": { + "title": "Block tracer type", + "type": "string", + "enum": ["callTracer", "prestateTracer"] + }, + "TransactionWithSender": { + "title": "Transaction object with sender", + "type": "object", + "allOf": [ + { + "required": ["from"], + "properties": { + "from": { + "title": "from", + "$ref": "#/components/schemas/address" + } + } + }, + { + "$ref": "#/components/schemas/TransactionUnsigned" + } + ] + }, + "Transaction1559Unsigned": { + "type": "object", + "title": "EIP-1559 transaction.", + "required": [ + "type", + "nonce", + "gas", + "value", + "input", + "maxFeePerGas", + "maxPriorityFeePerGas", + "chainId", + "accessList" + ], + "properties": { + "type": { + "title": "type", + "$ref": "#/components/schemas/byte" + }, + "nonce": { + "title": "nonce", + "$ref": "#/components/schemas/uint" + }, + "to": { + "title": "to address", + "$ref": "#/components/schemas/address" + }, + "gas": { + "title": "gas limit", + "$ref": "#/components/schemas/uint" + }, + "value": { + "title": "value", + "$ref": "#/components/schemas/uint" + }, + "input": { + "title": "input data", + "$ref": "#/components/schemas/bytes" + }, + "maxPriorityFeePerGas": { + "title": "max priority fee per gas", + "description": "Maximum fee per gas the sender is willing to pay to miners in wei", + "$ref": "#/components/schemas/uint" + }, + "maxFeePerGas": { + "title": "max fee per gas", + "description": "The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei", + "$ref": "#/components/schemas/uint" + }, + "accessList": { + "title": "accessList", + "type": "array" + }, + "chainId": { + "title": "chainId", + "description": "Chain ID that this transaction is valid on.", + "$ref": "#/components/schemas/uint" + } + } + }, + "TransactionLegacyUnsigned": { + "type": "object", + "title": "Legacy transaction.", + "required": ["type", "nonce", "gas", "value", "input", "gasPrice"], + "properties": { + "type": { + "title": "type", + "$ref": "#/components/schemas/byte" + }, + "nonce": { + "title": "nonce", + "$ref": "#/components/schemas/uint" + }, + "to": { + "title": "to address", + "$ref": "#/components/schemas/address" + }, + "gas": { + "title": "gas limit", + "$ref": "#/components/schemas/uint" + }, + "value": { + "title": "value", + "$ref": "#/components/schemas/uint" + }, + "input": { + "title": "input data", + "$ref": "#/components/schemas/bytes" + }, + "gasPrice": { + "title": "gas price", + "description": "The gas price willing to be paid by the sender in wei", + "$ref": "#/components/schemas/uint" + }, + "chainId": { + "title": "chainId", + "description": "Chain ID that this transaction is valid on.", + "$ref": "#/components/schemas/uint" + } + } + }, + "Transaction1559Signed": { + "title": "Signed 1559 Transaction", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/Transaction1559Unsigned" + }, + { + "title": "EIP-1559 transaction signature properties.", + "required": ["yParity", "r", "s", "v"], + "properties": { + "yParity": { + "title": "yParity", + "description": "The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.", + "oneOf": [ + { + "$ref": "#/components/schemas/byte" + }, + { + "$ref": "#/components/schemas/null" + } + ] + }, + "r": { + "title": "r", + "$ref": "#/components/schemas/uint" + }, + "s": { + "title": "s", + "$ref": "#/components/schemas/uint" + }, + "v": { + "title": "v", + "description": "For backwards compatibility, `v` is optionally provided as an alternative to `yParity`. This field is DEPRECATED and all use of it should migrate to `yParity`.", + "oneOf": [ + { + "$ref": "#/components/schemas/byte" + }, + { + "$ref": "#/components/schemas/null" + } + ] + } + } + } + ] + }, + "TransactionUnsigned": { + "oneOf": [ + { + "$ref": "#/components/schemas/Transaction1559Unsigned" + }, + { + "$ref": "#/components/schemas/TransactionLegacyUnsigned" + } + ] + }, + "TransactionLegacySigned": { + "title": "Signed Legacy Transaction", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/TransactionLegacyUnsigned" + }, + { + "title": "Legacy transaction signature properties.", + "required": ["v", "r", "s"], + "properties": { + "v": { + "title": "v", + "oneOf": [ + { + "$ref": "#/components/schemas/uint" + }, + { + "$ref": "#/components/schemas/null" + } + ] + }, + "r": { + "title": "r", + "$ref": "#/components/schemas/uint" + }, + "s": { + "title": "s", + "$ref": "#/components/schemas/uint" + } + } + } + ] + }, + "TransactionSigned": { + "anyOf": [ + { + "$ref": "#/components/schemas/Transaction1559Signed" + }, + { + "$ref": "#/components/schemas/TransactionLegacySigned" + } + ] + }, + "TransactionInfo": { + "type": "object", + "title": "Transaction information", + "allOf": [ + { + "title": "Contextual information", + "required": ["blockHash", "blockNumber", "from", "hash", "transactionIndex"], + "properties": { + "blockHash": { + "title": "block hash", + "$ref": "#/components/schemas/hash32" + }, + "blockNumber": { + "title": "block number", + "$ref": "#/components/schemas/uint" + }, + "from": { + "title": "from address", + "$ref": "#/components/schemas/address" + }, + "hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/hash32" + }, + "transactionIndex": { + "title": "transaction index", + "$ref": "#/components/schemas/uint" + } + } + }, + { + "$ref": "#/components/schemas/TransactionSigned" + } + ] + }, + "Log": { + "title": "log", + "type": "object", + "required": ["transactionHash"], + "properties": { + "removed": { + "title": "removed", + "type": "boolean" + }, + "logIndex": { + "title": "log index", + "$ref": "#/components/schemas/uint" + }, + "transactionIndex": { + "title": "transaction index", + "$ref": "#/components/schemas/uint" + }, + "transactionHash": { + "title": "transaction hash", + "$ref": "#/components/schemas/hash32" + }, + "blockHash": { + "title": "block hash", + "$ref": "#/components/schemas/hash32" + }, + "blockNumber": { + "title": "block number", + "$ref": "#/components/schemas/uint" + }, + "address": { + "title": "address", + "$ref": "#/components/schemas/address" + }, + "data": { + "title": "data", + "$ref": "#/components/schemas/bytes" + }, + "topics": { + "title": "topics", + "type": "array", + "items": { + "$ref": "#/components/schemas/bytes32" + } + } + } + }, + "ReceiptInfo": { + "type": "object", + "title": "Receipt info", + "required": [ + "blockHash", + "blockNumber", + "from", + "cumulativeGasUsed", + "gasUsed", + "logs", + "logsBloom", + "transactionHash", + "transactionIndex", + "effectiveGasPrice" + ], + "properties": { + "transactionHash": { + "title": "transaction hash", + "$ref": "#/components/schemas/hash32" + }, + "transactionIndex": { + "title": "transaction index", + "$ref": "#/components/schemas/uint" + }, + "blockHash": { + "title": "block hash", + "$ref": "#/components/schemas/hash32" + }, + "blockNumber": { + "title": "block number", + "$ref": "#/components/schemas/uint" + }, + "from": { + "title": "from", + "$ref": "#/components/schemas/address" + }, + "to": { + "title": "to", + "description": "Address of the receiver or null in a contract creation transaction.", + "$ref": "#/components/schemas/address" + }, + "cumulativeGasUsed": { + "title": "cumulative gas used", + "description": "The sum of gas used by this transaction and all preceding transactions in the same block.", + "$ref": "#/components/schemas/uint" + }, + "gasUsed": { + "title": "gas used", + "description": "The amount of gas used for this specific transaction alone.", + "$ref": "#/components/schemas/uint" + }, + "contractAddress": { + "title": "contract address", + "description": "The contract address created, if the transaction was a contract creation, otherwise null.", + "$ref": "#/components/schemas/address" + }, + "logs": { + "title": "logs", + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" + } + }, + "logsBloom": { + "title": "logs bloom", + "$ref": "#/components/schemas/bytes256" + }, + "root": { + "title": "state root", + "description": "The post-transaction state root. Only specified for transactions included before the Byzantium upgrade.", + "$ref": "#/components/schemas/bytes32" + }, + "status": { + "title": "status", + "description": "Either 1 (success) or 0 (failure). Only specified for transactions included after the Byzantium upgrade.", + "$ref": "#/components/schemas/uint" + }, + "effectiveGasPrice": { + "title": "effective gas price", + "description": "The actual value per gas deducted from the senders account. Before EIP-1559, this is equal to the transaction's gas price. After, it is equal to baseFeePerGas + min(maxFeePerGas - baseFeePerGas, maxPriorityFeePerGas).", + "$ref": "#/components/schemas/uint" + } + } + }, + "FilterResults": { + "title": "Filter results", + "oneOf": [ + { + "title": "new block hashes", + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + }, + { + "title": "new transaction hashes", + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + }, + { + "title": "new logs", + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" + } + } + ] + }, + "Filter": { + "title": "filter", + "type": "object", + "properties": { + "fromBlock": { + "title": "from block", + "$ref": "#/components/schemas/uint" + }, + "toBlock": { + "title": "to block", + "$ref": "#/components/schemas/uint" + }, + "blockHash": { + "title": "Block hash", + "$ref": "#/components/schemas/hash32" + }, + "address": { + "title": "Address(es)", + "oneOf": [ + { + "title": "Address", + "$ref": "#/components/schemas/address" + }, + { + "title": "Addresses", + "$ref": "#/components/schemas/addresses" + } + ] + }, + "topics": { + "title": "Topics", + "$ref": "#/components/schemas/FilterTopics" + } + } + }, + "FilterTopics": { + "title": "Filter Topics", + "type": "array", + "items": { + "$ref": "#/components/schemas/FilterTopic" + } + }, + "FilterTopic": { + "title": "Filter Topic List Entry", + "oneOf": [ + { + "title": "Any Topic Match", + "type": "null" + }, + { + "title": "Single Topic Match", + "$ref": "#/components/schemas/bytes32" + }, + { + "title": "Multiple Topic Match", + "type": "array", + "items": { + "$ref": "#/components/schemas/bytes32" + } + } + ] + }, + "LogFilter": { + "title": "log filter", + "type": "object", + "properties": { + "fromBlock": { + "title": "from block", + "$ref": "#/components/schemas/uint" + }, + "toBlock": { + "title": "to block", + "$ref": "#/components/schemas/uint" + }, + "address": { + "title": "Address", + "$ref": "#/components/schemas/address" + }, + "topics": { + "title": "Topics", + "$ref": "#/components/schemas/FilterTopics" + } + } + }, + "unsupportedError": { + "name": "Unsupported Error", + "schema": { + "type": "object", + "properties": { + "code": { + "title": "Error code", + "type": "number", + "pattern": "-32601" + }, + "message": { + "title": "Error message", + "type": "string", + "pattern": "Unsupported JSON-RPC method" + }, + "name": { + "title": "Error name", + "type": "string", + "pattern": "Method not found" + } + } + } + }, + "callframe": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["CALL", "CREATE"], + "description": "The type of action (CALL for function calls or CREATE for contract creation)." + }, + "from": { + "$ref": "#/components/schemas/address", + "description": "The address of the sender." + }, + "to": { + "$ref": "#/components/schemas/address", + "description": "The address of the receiver. In the case of a contract creation, this might be null." + }, + "value": { + "$ref": "#/components/schemas/uint", + "description": "The value transferred with the call, in hexadecimal." + }, + "gas": { + "$ref": "#/components/schemas/uint", + "description": "The gas provided for the call, in hexadecimal." + }, + "gasUsed": { + "$ref": "#/components/schemas/uint", + "description": "The gas used during the call, in hexadecimal." + }, + "input": { + "$ref": "#/components/schemas/bytes", + "description": "The input data sent with the call." + }, + "output": { + "$ref": "#/components/schemas/bytes", + "description": "The output data returned by the call." + }, + "error": { + "type": "string", + "description": "Error encountered during the call, if any." + }, + "revertReason": { + "type": "string", + "description": "Solidity revert reason, if applicable." + }, + "calls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/subcall" + }, + "description": "Sub-calls made during this call frame." + } + }, + "required": ["from", "gas", "input"] + } + } + } +} diff --git a/scripts/openrpc-json-updater/operations/merge.js b/scripts/openrpc-json-updater/operations/merge.js new file mode 100644 index 0000000000..1abd97ceef --- /dev/null +++ b/scripts/openrpc-json-updater/operations/merge.js @@ -0,0 +1,244 @@ +import { getMethodMap, getDifferingKeys } from '../utils/openrpc.utils.js'; +import {shouldSkipPath, shouldSkipKey, shouldSkipMethod} from '../config.js'; +import { + setNestedValue, + getNestedValue, + hasNestedPath, + removeSkippedKeys, + handleRefField, + getObjectByPath, + setObjectByPath, + findRefPaths, + handleRefFieldsWithOriginal, + filterSkippedMethods +} from '../utils/merge.utils.js'; + +class MergeDocuments { + /** + * Merges two OpenRPC documents + * @param {Object} originalJson - The original OpenRPC document + * @param {Object} modifiedJson - The modified Hedera OpenRPC document + * @returns {Object} - The merged OpenRPC document + */ + mergeDocuments(originalJson, modifiedJson) { + if (!Array.isArray(originalJson.methods)) return modifiedJson; + if (!Array.isArray(modifiedJson.methods)) modifiedJson.methods = []; + + // Step 1: Filter methods that should be skipped + const filteredOriginal = this.filterDocument(originalJson); + const filteredModified = this.filterDocument(modifiedJson); + + // Step 2: Merge methods from original to modified + this.mergeMethods(filteredOriginal, filteredModified); + + // Step 3: Merge components from original to modified + this.mergeComponents(filteredOriginal, filteredModified); + + // Step 4: Process the final document + return this.processDocument(filteredModified, filteredOriginal); + } + + /** + * Filters a document to remove methods that should be skipped + * @param {Object} document - The document to filter + * @returns {Object} - The filtered document + */ + filterDocument(document) { + const filtered = JSON.parse(JSON.stringify(document)); + filtered.methods = filterSkippedMethods(filtered.methods); + return filtered; + } + + /** + * Merges methods from original to modified + * @param {Object} filteredOriginal - The filtered original document + * @param {Object} filteredModified - The filtered modified document + */ + mergeMethods(filteredOriginal, filteredModified) { + const modifiedMap = getMethodMap(filteredModified); + + for (const origMethod of filteredOriginal.methods) { + const name = origMethod.name; + if (!name) continue; + + // If method doesn't exist in modified, add it + if (!modifiedMap.has(name)) { + filteredModified.methods.push(origMethod); + continue; + } + + const modMethod = modifiedMap.get(name); + + // Process $ref fields + this.processRefFields(origMethod, modMethod); + + // Process differing keys + this.processDifferingKeys(origMethod, modMethod); + } + } + + /** + * Processes $ref fields in a method + * @param {Object} origMethod - The original method + * @param {Object} modMethod - The modified method + */ + processRefFields(origMethod, modMethod) { + const refPaths = findRefPaths(origMethod); + + for (const { path } of refPaths) { + const targetObj = path ? getObjectByPath(modMethod, path) : modMethod; + const origObj = path ? getObjectByPath(origMethod, path) : origMethod; + + if (targetObj && typeof targetObj === 'object' && origObj && typeof origObj === 'object') { + if (path) { + setObjectByPath(modMethod, path, JSON.parse(JSON.stringify(origObj))); + } else { + for (const key in modMethod) { + delete modMethod[key]; + } + for (const key in origObj) { + modMethod[key] = origObj[key]; + } + } + } + } + } + + /** + * Processes differing keys between original and modified methods + * @param {Object} origMethod - The original method + * @param {Object} modMethod - The modified method + */ + processDifferingKeys(origMethod, modMethod) { + const differingKeys = getDifferingKeys(origMethod, modMethod); + + for (const path of differingKeys) { + if (shouldSkipPath(path)) continue; + + const methodName = origMethod.name; + if (shouldSkipMethod(methodName, path)) continue; + + const valueFromOriginal = getNestedValue(origMethod, path); + + const existsInOriginal = hasNestedPath(origMethod, path); + const existsInModified = hasNestedPath(modMethod, path); + + if (!existsInOriginal && existsInModified) { + continue; + } + + // Skip if path contains a $ref + if (this.shouldSkipDueToRef(modMethod, path)) continue; + + // Set the value from original to modified + if (path.includes('.')) { + setNestedValue(modMethod, path, valueFromOriginal); + } else { + modMethod[path] = valueFromOriginal; + } + } + } + + /** + * Checks if a path should be skipped due to containing a $ref + * @param {Object} obj - The object to check + * @param {string} path - The path to check + * @returns {boolean} - Whether the path should be skipped + */ + shouldSkipDueToRef(obj, path) { + const parts = path.split('.'); + let checkPath = ''; + for (let i = 0; i < parts.length; i++) { + checkPath = checkPath ? `${checkPath}.${parts[i]}` : parts[i]; + const value = getNestedValue(obj, checkPath); + if (value && typeof value === 'object' && value['$ref'] !== undefined) { + return true; + } + } + return false; + } + + /** + * Merges components from original to modified + * @param {Object} filteredOriginal - The filtered original document + * @param {Object} filteredModified - The filtered modified document + */ + mergeComponents(filteredOriginal, filteredModified) { + if (!filteredOriginal.components || typeof filteredOriginal.components !== 'object') { + return; + } + + if (!filteredModified.components) { + filteredModified.components = {}; + } + + for (const section in filteredOriginal.components) { + if (!filteredModified.components[section]) { + filteredModified.components[section] = {}; + } + + for (const key in filteredOriginal.components[section]) { + if (shouldSkipKey(key)) continue; + + if (!filteredModified.components[section][key]) { + filteredModified.components[section][key] = + removeSkippedKeys(filteredOriginal.components[section][key]); + } + } + } + } + + /** + * Processes a document to handle $ref fields and remove skipped keys + * @param {Object} document - The document to process + * @param {Object} originalDocument - The original document + * @returns {Object} - The processed document + */ + processDocument(document, originalDocument) { + if (!document || !document.methods || !Array.isArray(document.methods)) { + return document; + } + + const result = JSON.parse(JSON.stringify(document)); + + // Filter methods that should be skipped + result.methods = result.methods.filter(method => { + const methodName = method?.name; + if (!methodName) return true; + return !shouldSkipMethod(methodName); + }); + + // Remove skipped keys + result.methods = result.methods.map(method => removeSkippedKeys(method)); + + if (originalDocument) { + if (result.methods && originalDocument.methods) { + const origMethodMap = getMethodMap(originalDocument); + result.methods = result.methods.map(method => { + const origMethod = origMethodMap.get(method.name); + return origMethod ? handleRefFieldsWithOriginal(method, origMethod) : method; + }); + } + + if (result.components && originalDocument.components) { + result.components = handleRefFieldsWithOriginal(result.components, originalDocument.components, true); + } + + return result; + } + + return handleRefField(result); + } +} + +const mergeDocumentsInstance = new MergeDocuments(); + +/** + * Merges two OpenRPC documents + * @param {Object} originalJson - The original OpenRPC document + * @param {Object} modifiedJson - The modified OpenRPC document + * @returns {Object} - The merged OpenRPC document + */ +export function mergeDocuments(originalJson, modifiedJson) { + return mergeDocumentsInstance.mergeDocuments(originalJson, modifiedJson); +} diff --git a/scripts/openrpc-json-updater/operations/prepare.js b/scripts/openrpc-json-updater/operations/prepare.js new file mode 100644 index 0000000000..17ecdd3721 --- /dev/null +++ b/scripts/openrpc-json-updater/operations/prepare.js @@ -0,0 +1,19 @@ +import diff from 'deep-diff'; + +export function prepareDocuments(originalJson, modifiedJson) { + return { + normalizedOriginal: normalizeDocument(originalJson), + normalizedModified: normalizeDocument(modifiedJson) + }; +} + +export function normalizeDocument(document) { + return JSON.parse(JSON.stringify(document)); +} + +export function compareIgnoringFormatting(obj1, obj2) { + const normalized1 = normalizeDocument(obj1); + const normalized2 = normalizeDocument(obj2); + + return diff(normalized1, normalized2); +} diff --git a/scripts/openrpc-json-updater/operations/report.js b/scripts/openrpc-json-updater/operations/report.js new file mode 100644 index 0000000000..f6fa6438c6 --- /dev/null +++ b/scripts/openrpc-json-updater/operations/report.js @@ -0,0 +1,64 @@ +import { getMethodMap, getDifferingKeysByCategory, groupPaths } from '../utils/openrpc.utils.js'; +import { getSkippedMethodCategory } from '../config.js'; + +export async function generateReport( + originalJson, + modifiedJson +) { + const originalMethods = getMethodMap(originalJson); + const modifiedMethods = getMethodMap(modifiedJson); + + const missingMethods = []; + for (const name of originalMethods.keys()) { + if (!modifiedMethods.has(name)) { + const category = getSkippedMethodCategory(name); + + missingMethods.push({ + missingMethod: name, + status: category ? `${category}` : 'a new method' + }); + } + } + + const changedMethods = []; + for (const [name, origMethod] of originalMethods) { + if (!modifiedMethods.has(name)) continue; + const modMethod = modifiedMethods.get(name); + + const { valueDiscrepancies, customFields } = getDifferingKeysByCategory(origMethod, modMethod); + + if (valueDiscrepancies.length > 0 || customFields.length > 0) { + changedMethods.push({ + method: name, + valueDiscrepancies: groupPaths(valueDiscrepancies, 3), + customFields: groupPaths(customFields, 3) + }); + } + } + + if (missingMethods.length === 0 && changedMethods.length === 0) { + console.log('No differences detected.'); + return; + } + + if (missingMethods.length > 0) { + console.log( + '\nMethods present in the original document but missing from the modified document:\n' + ); + console.table(missingMethods); + + console.log('\nStatus explanation:'); + console.log('- (discarded): Methods that have been intentionally removed'); + console.log('- (not implemented): Methods that have not been implemented yet'); + } + + if (changedMethods.length > 0) { + console.log('\nMethods with differences between documents:\n'); + console.table(changedMethods, ['method', 'valueDiscrepancies', 'customFields']); + + console.log('\nExplanation:'); + console.log('- valueDiscrepancies: Fields that exist in both documents but have different values'); + console.log('- customFields: Fields that exist only in the modified document (custom additions)'); + console.log('- Entries with format "path (N diffs)" indicate N differences within that path'); + } +} diff --git a/scripts/openrpc-json-updater/original-openrpc.json b/scripts/openrpc-json-updater/original-openrpc.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/scripts/openrpc-json-updater/original-openrpc.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/scripts/openrpc-json-updater/package-lock.json b/scripts/openrpc-json-updater/package-lock.json new file mode 100644 index 0000000000..9212c7cc49 --- /dev/null +++ b/scripts/openrpc-json-updater/package-lock.json @@ -0,0 +1,18 @@ +{ + "name": "openrpc-json-updater", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "deep-diff": "^1.0.2" + } + }, + "node_modules/deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", + "license": "MIT" + } + } +} diff --git a/scripts/openrpc-json-updater/package.json b/scripts/openrpc-json-updater/package.json new file mode 100644 index 0000000000..7a3187a40d --- /dev/null +++ b/scripts/openrpc-json-updater/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "deep-diff": "^1.0.2" + } +} diff --git a/scripts/openrpc-json-updater/utils/file.utils.js b/scripts/openrpc-json-updater/utils/file.utils.js new file mode 100644 index 0000000000..5b48f50572 --- /dev/null +++ b/scripts/openrpc-json-updater/utils/file.utils.js @@ -0,0 +1,75 @@ +import fs from 'fs'; + +export function readJson(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf-8'); + return { + data: JSON.parse(content), + originalContent: content + }; + } catch (err) { + console.error(`Unable to read or parse "${filePath}":`, err); + process.exit(1); + } +} + +function formatJson(obj, indent = ' ', level = 0) { + const currentIndent = indent.repeat(level); + const nextIndent = indent.repeat(level + 1); + + if (obj === null || obj === undefined) { + return 'null'; + } + + if (typeof obj === 'number' || typeof obj === 'boolean') { + return String(obj); + } + + if (typeof obj === 'string') { + return JSON.stringify(obj); + } + + if (Array.isArray(obj)) { + if (obj.length <= 8 && obj.every(item => + (typeof item === 'string' && item.length < 40) || + (typeof item === 'number') || + (typeof item === 'boolean'))) { + const items = obj.map(item => formatJson(item, indent)).join(', '); + return `[${items}]`; + } + + const items = obj.map(item => nextIndent + formatJson(item, indent, level + 1)).join(',\n'); + return items.length > 0 ? `[\n${items}\n${currentIndent}]` : '[]'; + } + + const entries = Object.entries(obj); + if (entries.length === 0) { + return '{}'; + } + + const props = entries.map(([key, value]) => { + const formattedValue = formatJson(value, indent, level + 1); + return `${nextIndent}"${key}": ${formattedValue}`; + }).join(',\n'); + + return `{\n${props}\n${currentIndent}}`; +} + +export function writeJson(filePath, data, originalContent) { + try { + if (!originalContent) { + fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8'); + return true; + } + + const eol = originalContent.includes('\r\n') ? '\r\n' : '\n'; + const formatted = formatJson(data); + + const output = formatted.replace(/\n/g, eol); + + fs.writeFileSync(filePath, output, 'utf-8'); + return true; + } catch (err) { + console.error(`Unable to write to "${filePath}":`, err); + } +} diff --git a/scripts/openrpc-json-updater/utils/merge.utils.js b/scripts/openrpc-json-updater/utils/merge.utils.js new file mode 100644 index 0000000000..99da5830b8 --- /dev/null +++ b/scripts/openrpc-json-updater/utils/merge.utils.js @@ -0,0 +1,205 @@ +import { shouldSkipKey, shouldSkipMethod } from '../config.js'; + +/** + * Sets a nested value in an object using a dot-notation path + */ +export function setNestedValue(obj, path, value) { + if (!path) return; + + const parts = path.split('.'); + let current = obj; + + for (let i = 0; i < parts.length - 1; i++) { + const part = parts[i]; + + if (current[part] === undefined || current[part] === null || typeof current[part] !== 'object') { + current[part] = {}; + } + + current = current[part]; + } + + const lastPart = parts[parts.length - 1]; + current[lastPart] = value; +} + +/** + * Gets a nested value from an object using a dot-notation path + */ +export function getNestedValue(obj, path) { + if (!path) return undefined; + + const parts = path.split('.'); + let current = obj; + + for (const part of parts) { + if (current === undefined || current === null || typeof current !== 'object') { + return undefined; + } + current = current[part]; + } + + return current; +} + +/** + * Checks if a nested path exists in an object + */ +export function hasNestedPath(obj, path) { + const value = getNestedValue(obj, path); + return value !== undefined; +} + +/** + * Removes keys that should be skipped from an object + */ +export function removeSkippedKeys(obj) { + if (!obj || typeof obj !== 'object') return obj; + + if (Array.isArray(obj)) { + return obj.map(item => removeSkippedKeys(item)); + } + + const result = {}; + for (const key in obj) { + if (shouldSkipKey(key)) continue; + + result[key] = removeSkippedKeys(obj[key]); + } + + return result; +} + +/** + * Handles $ref fields in an object + */ +export function handleRefField(obj) { + if (!obj || typeof obj !== 'object') return obj; + + if (Array.isArray(obj)) { + return obj.map(item => handleRefField(item)); + } + + if (obj['$ref'] !== undefined) { + return { '$ref': obj['$ref'] }; + } + + const result = {}; + for (const key in obj) { + result[key] = handleRefField(obj[key]); + } + + return result; +} + +/** + * Gets an object by path from an object + */ +export function getObjectByPath(obj, path) { + const parts = path.split('.'); + let current = obj; + + for (const part of parts) { + if (current === undefined || current === null || typeof current !== 'object') { + return undefined; + } + current = current[part]; + } + + return current; +} + +/** + * Sets an object by path in an object + */ +export function setObjectByPath(obj, path, value) { + const parts = path.split('.'); + let current = obj; + + for (let i = 0; i < parts.length - 1; i++) { + const part = parts[i]; + + if (current[part] === undefined || current[part] === null || typeof current[part] !== 'object') { + current[part] = {}; + } + + current = current[part]; + } + + const lastPart = parts[parts.length - 1]; + current[lastPart] = value; +} + +/** + * Finds all paths in an object that contain $ref fields + */ +export function findRefPaths(obj, currentPath = '', paths = []) { + if (!obj || typeof obj !== 'object') return paths; + + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + findRefPaths(obj[i], currentPath ? `${currentPath}.${i}` : `${i}`, paths); + } + } else { + if (obj['$ref'] !== undefined) { + paths.push({ + path: currentPath, + ref: obj['$ref'] + }); + } + + for (const key in obj) { + if (typeof obj[key] === 'object' && obj[key] !== null) { + findRefPaths(obj[key], currentPath ? `${currentPath}.${key}` : key, paths); + } + } + } + + return paths; +} + +/** + * Handles $ref fields with an original object + */ +export function handleRefFieldsWithOriginal(obj, origObj, isComponent = false) { + if (!obj || typeof obj !== 'object') return obj; + if (!origObj || typeof origObj !== 'object') return isComponent ? obj : handleRefField(obj); + + if (Array.isArray(obj)) { + return obj.map((item, index) => + index < origObj.length ? handleRefFieldsWithOriginal(item, origObj[index], isComponent) : (isComponent ? item : handleRefField(item)) + ); + } + + if (origObj['$ref'] !== undefined) { + return JSON.parse(JSON.stringify(origObj)); + } + + if (obj['$ref'] !== undefined) { + if (isComponent) { + return JSON.parse(JSON.stringify(obj)); + } + return { '$ref': obj['$ref'] }; + } + + const newObj = {}; + for (const key in obj) { + const origValue = origObj[key]; + newObj[key] = handleRefFieldsWithOriginal(obj[key], origValue, isComponent); + } + + return newObj; +} + +/** + * Filters methods that should be skipped + */ +export function filterSkippedMethods(methods) { + if (!Array.isArray(methods)) return []; + + return methods.filter(method => { + const methodName = method?.name; + if (!methodName) return true; + return !shouldSkipMethod(methodName); + }); +} diff --git a/scripts/openrpc-json-updater/utils/openrpc.utils.js b/scripts/openrpc-json-updater/utils/openrpc.utils.js new file mode 100644 index 0000000000..06f100fe43 --- /dev/null +++ b/scripts/openrpc-json-updater/utils/openrpc.utils.js @@ -0,0 +1,162 @@ +import { compareIgnoringFormatting } from '../operations/prepare.js'; +import { shouldSkipPath, shouldSkipKey } from '../config.js'; + +export function getMethodMap(openrpcDoc) { + const map = new Map(); + if (Array.isArray(openrpcDoc.methods)) { + for (const m of openrpcDoc.methods) { + if (m?.name) map.set(m.name, m); + } + } + return map; +} + +function hasKey(obj, path) { + if (!path) return false; + + const parts = path.split('.'); + let current = obj; + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + + if (current === undefined || current === null || typeof current !== 'object') { + return false; + } + + if (!(part in current)) { + return false; + } + + current = current[part]; + } + + return true; +} + +export function groupPaths(paths, minGroupSize = 3) { + if (!paths || paths.length === 0) return '-'; + if (paths.length === 1) return paths[0]; + + function getDepth(path) { + return path.split('.').length; + } + + function analyzePrefixes(paths) { + const prefixCounters = {}; + + for (const path of paths) { + const parts = path.split('.'); + let currentPrefix = ''; + + for (let i = 0; i < parts.length - 1; i++) { + currentPrefix = currentPrefix ? `${currentPrefix}.${parts[i]}` : parts[i]; + prefixCounters[currentPrefix] = (prefixCounters[currentPrefix] || 0) + 1; + } + } + + return Object.keys(prefixCounters) + .filter(prefix => prefixCounters[prefix] >= minGroupSize) + .sort((a, b) => { + const countDiff = prefixCounters[b] - prefixCounters[a]; + if (countDiff !== 0) return countDiff; + + const depthA = getDepth(a); + const depthB = getDepth(b); + return depthB - depthA; + }); + } + + function getSubpaths(paths, prefix) { + return paths.filter(path => path.startsWith(prefix + '.') || path === prefix); + } + + function groupPathsHierarchically(paths) { + const remainingPaths = [...paths]; + const result = []; + + const commonPrefixes = analyzePrefixes(paths); + + for (const prefix of commonPrefixes) { + const matchingPaths = getSubpaths(remainingPaths, prefix); + + if (matchingPaths.length >= minGroupSize) { + for (const path of matchingPaths) { + const index = remainingPaths.indexOf(path); + if (index !== -1) { + remainingPaths.splice(index, 1); + } + } + + result.push(`${prefix} (${matchingPaths.length} diffs)`); + } + } + + result.push(...remainingPaths); + + return result; + } + + const groupedPaths = groupPathsHierarchically(paths); + return groupedPaths.join(', '); +} + +export function getDifferingKeysByCategory(origMethod, modMethod) { + const result = { + valueDiscrepancies: [], + customFields: [] + }; + + const differences = compareIgnoringFormatting(origMethod, modMethod) || []; + + for (const d of differences) { + if (d.path) { + const fullPath = d.path.join('.'); + + if (!fullPath || fullPath.startsWith('name')) continue; + + if (shouldSkipPath(fullPath)) continue; + + if (hasKey(origMethod, fullPath) && hasKey(modMethod, fullPath)) { + result.valueDiscrepancies.push(fullPath); + } + else if (!hasKey(origMethod, fullPath) && hasKey(modMethod, fullPath)) { + result.customFields.push(fullPath); + } + } + } + + function findMissingKeys(prefix, orig, mod) { + for (const key in orig) { + if (shouldSkipKey(key)) continue; + + const newPrefix = prefix ? `${prefix}.${key}` : key; + + if (newPrefix === 'name') continue; + + if (shouldSkipPath(newPrefix)) continue; + + if (!(key in mod)) { + result.valueDiscrepancies.push(newPrefix); + } else if ( + typeof orig[key] === 'object' && + orig[key] !== null && + typeof mod[key] === 'object' && + mod[key] !== null && + !Array.isArray(orig[key]) && + !Array.isArray(mod[key]) + ) { + findMissingKeys(newPrefix, orig[key], mod[key]); + } + } + } + + findMissingKeys('', origMethod, modMethod); + + return result; +} + +export function getDifferingKeys(origMethod, modMethod) { + const { valueDiscrepancies, customFields } = getDifferingKeysByCategory(origMethod, modMethod); + return [...new Set([...valueDiscrepancies, ...customFields])]; +} From 8bd62127ff0a62ae08594c597f67f110804ecec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Tue, 3 Jun 2025 14:16:06 +0200 Subject: [PATCH 02/17] chore: update OpenRPC paths, enhance README, and pin GitHub Actions versions (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- .github/workflows/openrpc-updater.yml | 14 +- scripts/openrpc-json-updater/README.md | 9 + scripts/openrpc-json-updater/cli.js | 2 +- .../modified-openrpc.json | 2169 ----------------- 4 files changed, 16 insertions(+), 2178 deletions(-) delete mode 100644 scripts/openrpc-json-updater/modified-openrpc.json diff --git a/.github/workflows/openrpc-updater.yml b/.github/workflows/openrpc-updater.yml index be779d58a0..24f45c7c9f 100644 --- a/.github/workflows/openrpc-updater.yml +++ b/.github/workflows/openrpc-updater.yml @@ -42,7 +42,7 @@ jobs: needs: clone-and-build-execution-apis steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: 'main' token: ${{ secrets.PAT_TOKEN }} @@ -59,7 +59,7 @@ jobs: cp ./downloaded-artifacts/refs-openrpc.json scripts/openrpc-json-updater/original-openrpc.json - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '22' cache: 'npm' @@ -92,10 +92,8 @@ jobs: echo "No differences found. Skipping PR creation." echo "SKIP_PR=true" >> $GITHUB_ENV exit 0 - elif [[ "$MERGE_OUTPUT" =~ Merge\ completed\.\ Updated\ file:\ \'([^\']+)\'\. ]]; then - echo "Successfully updated modified-openrpc.json" - echo "MERGE_FILE=modified-openrpc.json" >> $GITHUB_ENV - echo "MERGE_FILENAME=modified-openrpc.json" >> $GITHUB_ENV + 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" @@ -110,7 +108,7 @@ jobs: id: branch-name run: | TIMESTAMP=$(date +%Y%m%d%H%M%S) - UNIQUE_BRANCH="${{ github.event.inputs.branch_name }}-${TIMESTAMP}" + UNIQUE_BRANCH="update-openrpc-${TIMESTAMP}" echo "UNIQUE_BRANCH=${UNIQUE_BRANCH}" >> $GITHUB_ENV echo "Generated unique branch name: ${UNIQUE_BRANCH}" @@ -133,5 +131,5 @@ jobs: branch: ${{ env.UNIQUE_BRANCH }} base: 'main' add-paths: | - scripts/openrpc-json-updater/modified-openrpc.json + docs/openrpc.json delete-branch: false \ No newline at end of file diff --git a/scripts/openrpc-json-updater/README.md b/scripts/openrpc-json-updater/README.md index 276f4d4982..3af31b4114 100644 --- a/scripts/openrpc-json-updater/README.md +++ b/scripts/openrpc-json-updater/README.md @@ -1,5 +1,14 @@ # 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 + ## Install ```shell script diff --git a/scripts/openrpc-json-updater/cli.js b/scripts/openrpc-json-updater/cli.js index ba6e9d47c6..994a543c2e 100644 --- a/scripts/openrpc-json-updater/cli.js +++ b/scripts/openrpc-json-updater/cli.js @@ -4,7 +4,7 @@ import { generateReport } from './operations/report.js'; import { prepareDocuments, compareIgnoringFormatting } from './operations/prepare.js'; const originalFilePath = './original-openrpc.json'; -const modifiedFilePath = './modified-openrpc.json'; +const modifiedFilePath = '../../docs/openrpc.json'; const { data: originalJson } = readJson(originalFilePath); const { data: modifiedJson, originalContent: modifiedContent } = readJson(modifiedFilePath); diff --git a/scripts/openrpc-json-updater/modified-openrpc.json b/scripts/openrpc-json-updater/modified-openrpc.json deleted file mode 100644 index c234eba6d9..0000000000 --- a/scripts/openrpc-json-updater/modified-openrpc.json +++ /dev/null @@ -1,2169 +0,0 @@ -{ - "openrpc": "1.0.0", - "info": { - "title": "Hedera JSON-RPC Specification", - "description": "A specification of the implemented Ethereum JSON RPC APIs interface for Hedera clients and adheres to the Ethereum execution APIs schema.", - "version": "0.69.0-SNAPSHOT" - }, - "servers": [ - { - "name": "Mainnet", - "url": "https://mainnet.hashio.io/api", - "description": "Hedera Mainnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." - }, - { - "name": "Testnet", - "url": "https://testnet.hashio.io/api", - "description": "Hedera Testnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." - }, - { - "name": "Previewnet", - "url": "https://previewnet.hashio.io/api", - "description": "Hedera Previewnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." - }, - { - "name": "localhost", - "url": "http://localhost:7546", - "description": "Run your own instance of [`hiero-json-rpc-relay`](https://github.com/hiero-ledger/hiero-json-rpc-relay) on `localhost`, and configure it connect to your choice of Hedera networks. No rate limits apply." - }, - { - "name": "WS Mainnet", - "url": "wss://mainnet.hashio.io/ws", - "description": "Hedera Web Socket Mainnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." - }, - { - "name": "WS Testnet", - "url": "wss://testnet.hashio.io/ws", - "description": "Hedera Web Socket Testnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." - }, - { - "name": "WS Previewnet", - "url": "wss://previewnet.hashio.io/ws", - "description": "Hedera Web Socket Previewnet endpoint, hosted by [Hashio](https://swirldslabs.com/hashio/), a JSON-RPC Relay Community Service. Rate limited based [on IP address](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rate-limiting.md) and [on global HBAR spend](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/hbar-limiting.md)." - }, - { - "name": "WS localhost", - "url": "ws://localhost:8546", - "description": "Run your own instance of Web Socket [`hiero-json-rpc-relay`](https://github.com/hiero-ledger/hiero-json-rpc-relay) on `localhost`, and configure it connect to your choice of Hedera networks. No rate limits apply." - } - ], - "methods": [ - { - "name": "eth_accounts", - "summary": "Returns a list of addresses owned by client.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "Accounts", - "description": "Always returns an empty array", - "schema": { - "title": "empty array", - "type": "array", - "pattern": "^\\[\\s*\\]$" - } - } - }, - { - "name": "eth_blockNumber", - "summary": "Returns the number of most recent block.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [], - "result": { - "name": "Block number", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_call", - "summary": "Executes a new message call immediately without creating a transaction on the block chain. ", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/TransactionWithSender" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Return data", - "schema": { - "$ref": "#/components/schemas/bytes" - } - } - }, - { - "name": "eth_chainId", - "summary": "Returns the chain ID of the current network.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [], - "result": { - "name": "Chain ID", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_coinbase", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - }, - { - "name": "eth_blobBaseFee", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - }, - { - "name": "eth_estimateGas", - "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/TransactionWithSender" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Gas used", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_feeHistory", - "summary": "Returns transaction base fee per gas and effective priority fee per gas for the requested/supported block range.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "blockCount", - "description": "Requested range of blocks. Clients will return less than the requested range if not all blocks are available.", - "required": true, - "schema": { - "$ref": "#/components/schemas/uint" - } - }, - { - "name": "newestBlock", - "description": "Highest block of the requested range.", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - }, - { - "name": "rewardPercentiles", - "description": "A monotonically increasing list of percentile values. For each block in the requested range, the transactions will be sorted in ascending order by effective tip per gas and the coresponding effective tip for the percentile will be determined, accounting for gas consumed.", - "required": true, - "schema": { - "title": "rewardPercentiles", - "type": "array", - "items": { - "title": "rewardPercentile", - "description": "Floating point value between 0 and 100.", - "type": "number", - "pattern": "^[0-9][0-9]?$|^100$" - } - } - } - ], - "result": { - "name": "feeHistoryResult", - "description": "Fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available.", - "schema": { - "title": "feeHistoryResults", - "description": "Fee history results.", - "type": "object", - "required": ["oldestBlock", "baseFeePerGas", "gasUsedRatio"], - "properties": { - "oldestBlock": { - "title": "oldestBlock", - "description": "Lowest number block of returned range.", - "$ref": "#/components/schemas/uint" - }, - "gasUsedRatio": { - "title": "gasUsedRatio", - "description": "An array of gas used ratio.", - "type": "array", - "items": { - "title": "floating point number", - "type": "number", - "pattern": "^([0-9].[0-9]*|0)$" - } - }, - "baseFeePerGas": { - "title": "baseFeePerGas", - "description": "An array of block base fees per gas. This includes the next block after the newest of the returned range.", - "type": "array", - "items": { - "$ref": "#/components/schemas/uint" - } - }, - "reward": { - "title": "reward", - "description": "A two-dimensional array of effective priority fees per gas at the requested block percentiles.", - "type": "array", - "items": { - "title": "rewardPercentile", - "description": "An array of effective priority fee per gas data points from a single block. All zeroes are returned if the block is empty.", - "type": "array", - "items": { - "title": "rewardPercentile", - "description": "A given percentile sample of effective priority fees per gas from a single block in ascending order, weighted by gas used. Zeroes are returned if the block is empty.", - "$ref": "#/components/schemas/uint" - } - } - } - } - } - } - }, - { - "name": "eth_gasPrice", - "summary": "Returns the current price per gas in weibars.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [], - "result": { - "name": "Gas price", - "schema": { - "title": "Gas price", - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_getBalance", - "summary": "Returns the balance of the account of given address.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Balance", - "schema": { - "title": "hex encoded unsigned integer", - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_getBlockByHash", - "summary": "Returns information about a block by hash.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Block hash", - "required": true, - "schema": { - "$ref": "#/components/schemas/hash32" - } - }, - { - "name": "Hydrated transactions", - "required": true, - "schema": { - "title": "hydrated", - "type": "boolean" - } - } - ], - "result": { - "name": "Block information", - "schema": { - "$ref": "#/components/schemas/Block" - } - } - }, - { - "name": "eth_getBlockByNumber", - "summary": "Returns information about a block by number.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - }, - { - "name": "Hydrated transactions", - "required": true, - "schema": { - "title": "hydrated", - "type": "boolean" - } - } - ], - "result": { - "name": "Block information", - "schema": { - "$ref": "#/components/schemas/Block" - } - } - }, - { - "name": "eth_getBlockTransactionCountByHash", - "summary": "Returns the number of transactions in a block from a block matching the given block hash.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "Block hash", - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - ], - "result": { - "name": "Transactions count", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_getBlockTransactionCountByNumber", - "summary": "Returns the number of transactions in a block matching the given block number.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "Block", - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Transactions count", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_getCode", - "summary": "Returns code at a given address.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Bytecode", - "schema": { - "$ref": "#/components/schemas/bytes" - } - } - }, - { - "name": "eth_getLogs", - "summary": "Returns an array of all logs matching a given filter object.", - "description": "The block range filter, _i.e._, `fromBlock` and `toBlock` arguments, cannot be larger than `ETH_GET_LOGS_BLOCK_RANGE_LIMIT` (defaults to `1000`). However, when `address` represents a single address, either a `string` or an array with a single element, this restriction is lifted. In any case, if the `topics` param is present the block range must be within ~`302,400` blocks, the equivalent to 7 days.\n\nWhen the logs for an individual address exceeds `MIRROR_NODE_CONTRACT_RESULTS_LOGS_PG_MAX * MIRROR_NODE_LIMIT_PARAM` (defaults to `20k`) an error `-32011` _Mirror Node pagination count range too large_ is returned. These settings are the Mirror Node page limit (defaults to `200`) and Mirror Node entries per page (defaults to `100`) respectively. ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Filter", - "schema": { - "$ref": "#/components/schemas/Filter" - } - } - ], - "result": { - "name": "Log objects", - "schema": { - "$ref": "#/components/schemas/FilterResults" - } - } - }, - { - "name": "eth_getStorageAt", - "summary": "Returns the value from a storage position at a given address and block. The optional block param may be latest or a valid tag of a historical block.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "Position", - "required": true, - "schema": { - "$ref": "#/components/schemas/uint" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTagOrHash" - } - } - ], - "result": { - "name": "Value from storage", - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - }, - { - "name": "eth_getTransactionByBlockHashAndIndex", - "summary": "Returns information about a transaction by block hash and transaction index position.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "Block hash", - "required": true, - "schema": { - "$ref": "#/components/schemas/hash32" - } - }, - { - "name": "Transaction index", - "required": true, - "schema": { - "$ref": "#/components/schemas/uint" - } - } - ], - "result": { - "name": "Transaction information", - "schema": { - "$ref": "#/components/schemas/TransactionInfo" - } - } - }, - { - "name": "eth_getTransactionByBlockNumberAndIndex", - "summary": "Returns information about a transaction by block number and transaction index position.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - }, - { - "name": "Transaction index", - "required": true, - "schema": { - "$ref": "#/components/schemas/uint" - } - } - ], - "result": { - "name": "Transaction information", - "schema": { - "$ref": "#/components/schemas/TransactionInfo" - } - } - }, - { - "name": "eth_getTransactionByHash", - "summary": "Returns the information about a transaction requested by transaction hash.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Transaction hash", - "required": true, - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - ], - "result": { - "name": "Transaction information", - "schema": { - "$ref": "#/components/schemas/TransactionInfo" - } - } - }, - { - "name": "eth_getTransactionCount", - "summary": "Returns the number of transactions sent from an address.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Transaction count", - "schema": { - "title": "Transaction count", - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_getTransactionReceipt", - "summary": "Returns the receipt of a transaction by transaction hash.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Transaction hash", - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - ], - "result": { - "name": "Receipt Information", - "schema": { - "$ref": "#/components/schemas/ReceiptInfo" - } - } - }, - { - "name": "eth_getUncleByBlockHashAndIndex", - "summary": "Returns information about a uncle of a block by hash and uncle index position.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "eth_getUncleByBlockHashAndIndex result", - "schema": { - "description": "Always returns null. There are no uncles in Hedera.", - "$ref": "#/components/schemas/null" - } - } - }, - { - "name": "eth_getUncleByBlockNumberAndIndex", - "summary": "Returns information about a uncle of a block by number and uncle index position.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "eth_getUncleByBlockNumberAndIndex result", - "schema": { - "description": "Always returns null. There are no uncles in Hedera.", - "$ref": "#/components/schemas/null" - } - } - }, - { - "name": "eth_getUncleCountByBlockHash", - "summary": "Returns the number of uncles in a block from a block matching the given block hash.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "eth_getUncleCountByBlockHash result", - "schema": { - "description": "Always returns '0x0'. There are no uncles in Hedera.", - "title": "hex encoded unsigned integer", - "type": "string", - "pattern": "0x0" - } - } - }, - { - "name": "eth_getUncleCountByBlockNumber", - "summary": "Returns the number of transactions in a block matching the given block number.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "eth_getUncleCountByBlockNumber result", - "schema": { - "description": "Always returns '0x0'. There are no uncles in Hedera.", - "title": "hex encoded unsigned integer", - "type": "string", - "pattern": "0x0" - } - } - }, - { - "name": "eth_getWork", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - }, - { - "name": "eth_hashrate", - "summary": "Returns the number of hashes per second that the node is mining with. Always returns 0x0.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "Mining status", - "schema": { - "title": "Hashrate", - "type": "string", - "pattern": "0x0" - } - } - }, - { - "name": "eth_maxPriorityFeePerGas", - "summary": "Returns a fee per gas that is an estimate of how much you can pay as a priority fee, or 'tip', to get a transaction included in the current block.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [], - "result": { - "name": "eth_maxPriorityFeePerGas result", - "schema": { - "description": "Always returns '0x0'. Hedera doesn't have a concept of tipping nodes to promote any behavior", - "title": "hex encoded unsigned integer", - "type": "string", - "pattern": "0x0" - } - } - }, - { - "name": "eth_mining", - "summary": "Returns whether the client is a miner. We don't mine, so this always returns false.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "Mining status", - "schema": { - "title": "miningStatus", - "type": "boolean" - } - } - }, - { - "name": "eth_newBlockFilter", - "summary": "Creates a filter object, to notify of newly created blocks.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "Filter ID", - "schema": { - "$ref": "#/components/schemas/hash16" - } - }, - "tags": [ - { - "name": "alpha API" - } - ] - }, - { - "name": "eth_newFilter", - "summary": "Creates a filter object, based on filter options, to notify when the state changes (logs).", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "LogFilter", - "schema": { - "$ref": "#/components/schemas/LogFilter" - } - } - ], - "result": { - "name": "Filter ID", - "schema": { - "$ref": "#/components/schemas/hash16" - } - }, - "tags": [ - { - "name": "alpha API" - } - ] - }, - { - "name": "eth_uninstallFilter", - "summary": "Uninstalls a filter with given id.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "FilterID", - "schema": { - "$ref": "#/components/schemas/hash16" - } - } - ], - "result": { - "name": "Uninstall status", - "schema": { - "type": "boolean" - } - }, - "tags": [ - { - "name": "alpha API" - } - ] - }, - { - "name": "eth_newPendingTransactionFilter", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - }, - "tags": [ - { - "name": "alpha API" - } - ] - }, - { - "name": "eth_getFilterLogs", - "summary": "Returns an array of all logs matching filter with given id.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "Filter ID", - "schema": { - "$ref": "#/components/schemas/hash16" - } - } - ], - "result": { - "name": "Filter result", - "schema": { - "$ref": "#/components/schemas/FilterResults" - } - }, - "tags": [ - { - "name": "alpha API" - } - ] - }, - { - "name": "eth_getFilterChanges", - "summary": "Gets the latest results since the last request for a provided filter according to its type.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "Filter ID", - "schema": { - "$ref": "#/components/schemas/hash16" - } - } - ], - "result": { - "name": "Filter result", - "schema": { - "oneOf": [ - { - "type": "array", - "items": { - "blockHash": { - "title": "block hash", - "$ref": "#/components/schemas/hash32" - } - } - }, - { - "name": "Log objects", - "schema": { - "$ref": "#/components/schemas/FilterResults" - } - } - ] - } - }, - "tags": [ - { - "name": "alpha API" - } - ] - }, - { - "name": "eth_protocolVersion", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - }, - { - "name": "eth_sendRawTransaction", - "summary": "Submits a raw transaction.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/bytes" - } - } - ], - "result": { - "name": "Transaction hash", - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - }, - { - "name": "eth_sendTransaction", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - }, - { - "name": "eth_signTransaction", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - }, - { - "name": "eth_sign", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - }, - { - "name": "eth_submitHashrate", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - }, - { - "name": "eth_submitWork", - "summary": "Used for submitting a proof-of-work solution.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "PoW solution", - "schema": { - "title": "No proof-of-work", - "description": "Always returns false.", - "type": "boolean" - } - } - }, - { - "name": "eth_syncing", - "summary": "Returns syncing status.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "Syncing status", - "schema": { - "title": "Not syncing", - "description": "Always returns false.", - "type": "boolean" - } - } - }, - { - "name": "net_listening", - "summary": "Returns true if client is actively listening for network connections.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "Listening status.", - "schema": { - "title": "Not listening", - "description": "Always returns false.", - "type": "boolean" - } - } - }, - { - "name": "net_version", - "summary": "Returns the current chain id.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [], - "result": { - "name": "The current chain id.", - "schema": { - "$ref": "#/components/schemas/int" - } - } - }, - { - "name": "net_peerCount", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - }, - { - "name": "web3_clientVersion", - "summary": "Returns the current client version.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [], - "result": { - "name": "The current client version.", - "schema": { - "type": "string", - "pattern": "relay/[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9-]+)?$" - } - } - }, - { - "name": "web3_sha3", - "summary": "Returns sha3 of the input.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png) ![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [], - "result": { - "name": "The sha3 result.", - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - }, - { - "name": "eth_subscribe", - "summary": "Creates a new subscription for desired events. Sends data as soon as it occurs.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "subscription_type", - "required": true, - "schema": { - "type": "string", - "enum": ["logs"] - }, - "description": "The type of event you want to subscribe to." - }, - { - "name": "options", - "required": false, - "schema": { - "type": "object", - "properties": { - "address": { - "oneOf": [ - { - "$ref": "#/components/schemas/address" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/address" - } - } - ], - "description": "Single address or array of addresses to restrict the logs." - }, - "topics": { - "$ref": "#/components/schemas/FilterTopics", - "description": "Array of topics used to filter logs." - } - }, - "additionalProperties": false - }, - "description": "Filter options for the subscription. Required for 'logs' type." - } - ], - "result": { - "name": "subscription_id", - "schema": { - "type": "string", - "$ref": "#/components/schemas/hash32" - }, - "description": "The hex encoded subscription ID used to identify and manage the subscription." - } - }, - { - "name": "eth_unsubscribe", - "summary": "Cancels a subscription.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/ws_label.png)", - "params": [ - { - "name": "subscription_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "The subscription ID to cancel." - } - ], - "result": { - "name": "success", - "schema": { - "type": "boolean" - }, - "description": "True if the subscription was successfully cancelled, otherwise false." - } - }, - { - "name": "debug_traceTransaction", - "summary": "Attempts to run the transaction in the exact same manner as it was executed on the network.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "transactionHash", - "required": true, - "schema": { - "$ref": "#/components/schemas/hash32" - }, - "description": "The hash of the transaction to trace." - }, - { - "name": "tracer", - "required": false, - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/TracerType" - }, - { - "$ref": "#/components/schemas/TracerConfig" - }, - { - "$ref": "#/components/schemas/TracerConfigWrapper" - } - ] - }, - "description": "Specifies the type of tracer or configuration object for the tracer." - }, - { - "name": "tracerConfig", - "required": false, - "schema": { - "$ref": "#/components/schemas/TracerConfig" - }, - "description": "Configuration object for the tracer." - } - ], - "result": { - "name": "trace", - "schema": { - "type": "object", - "properties": { - "callFrame": { - "$ref": "#/components/schemas/callframe" - } - }, - "required": ["callFrame"], - "additionalProperties": false - }, - "description": "The trace object containing detailed information about the transaction execution, encapsulated in a call frame." - } - }, - { - "name": "debug_traceBlockByNumber", - "summary": "Returns the tracing result for all transactions in the block specified by number with a tracer.", - "description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)", - "params": [ - { - "name": "blockNumber", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - }, - "description": "The block number or tag (e.g., 'latest', 'earliest', 'pending', 'safe', 'finalized')." - }, - { - "name": "tracerOptions", - "required": false, - "schema": { - "type": "object", - "properties": { - "tracer": { - "$ref": "#/components/schemas/BlockTracerType", - "description": "The type of tracer to use. Either 'callTracer' or 'prestateTracer'." - }, - "tracerConfig": { - "type": "object", - "description": "Configuration options for the specified tracer", - "properties": { - "onlyTopCall": { - "type": "boolean", - "description": "When set to true, this will only trace the primary (top-level) call and not any sub-calls." - } - } - } - } - }, - "description": "Specifies the type of tracer and optional configuration. Supported tracers are 'callTracer' and 'prestateTracer'." - } - ], - "result": { - "name": "traces", - "schema": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "object", - "title": "Call Tracer Response", - "description": "callTracer response format", - "properties": { - "type": { - "type": "string", - "enum": ["CALL", "CREATE"], - "description": "The type of action (CALL for function calls or CREATE for contract creation)." - }, - "from": { - "$ref": "#/components/schemas/address", - "description": "The address of the sender." - }, - "to": { - "$ref": "#/components/schemas/address", - "description": "The address of the receiver. In the case of a contract creation, this might be null." - }, - "value": { - "$ref": "#/components/schemas/uint", - "description": "The value transferred with the call, in hexadecimal." - }, - "gas": { - "$ref": "#/components/schemas/uint", - "description": "The gas provided for the call, in hexadecimal." - }, - "gasUsed": { - "$ref": "#/components/schemas/uint", - "description": "The gas used during the call, in hexadecimal." - }, - "input": { - "$ref": "#/components/schemas/bytes", - "description": "The input data sent with the call." - }, - "output": { - "$ref": "#/components/schemas/bytes", - "description": "The output data returned by the call." - }, - "error": { - "type": "string", - "description": "Error encountered during the call, if any." - }, - "revertReason": { - "type": "string", - "description": "Solidity revert reason, if applicable." - }, - "calls": { - "type": "array", - "items": { - "$ref": "#/components/schemas/subcall" - }, - "description": "Sub-calls made during this call frame." - } - } - }, - { - "type": "object", - "title": "Prestate Tracer Response", - "description": "prestateTracer response format - an object where keys are ethereum addresses and values contain account state data", - "properties": { - "balance": { - "$ref": "#/components/schemas/uint", - "description": "The balance of the account/contract, expressed in wei and encoded as a hexadecimal string" - }, - "nonce": { - "$ref": "#/components/schemas/uint", - "description": "The latest nonce of the account, represented as an unsigned integer. Historical nonces are not supported by now." - }, - "code": { - "$ref": "#/components/schemas/bytes", - "description": "The bytecode of the contract, encoded as a hexadecimal string. If the account is not a contract, this will be an empty hex string (0x)." - }, - "storage": { - "type": "object", - "description": "A map of key-value pairs representing the storage slots of the contract. The keys and values are both encoded as hexadecimal strings. If the account is not a contract, this will be an empty object ({}).", - "additionalProperties": { - "$ref": "#/components/schemas/bytes32" - } - } - } - } - ] - } - }, - "description": "An array of trace objects containing detailed information about all transactions in the block. For callTracer, each item is a call frame with transaction details. For prestateTracer, each item is an object mapping addresses to their account state." - } - }, - { - "name": "eth_getProof", - "summary": "Always returns UNSUPPORTED_METHOD error.", - "params": [], - "result": { - "$ref": "#/components/schemas/unsupportedError" - } - } - ], - "components": { - "schemas": { - "address": { - "title": "hex encoded address", - "type": "string", - "pattern": "^0x[0-9,a-f,A-F]{40}$" - }, - "addresses": { - "type": "array", - "items": { - "$ref": "#/components/schemas/address" - } - }, - "byte": { - "title": "hex encoded byte", - "type": "string", - "pattern": "^0x([0-9,a-f,A-F]?){1,2}$" - }, - "bytes": { - "title": "hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]*$" - }, - "bytes8": { - "title": "8 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{16}$" - }, - "bytes32": { - "title": "32 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{64}$" - }, - "bytes256": { - "title": "256 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{512}$" - }, - "bytes65": { - "title": "65 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{512}$" - }, - "int": { - "title": "decimal value integer", - "type": "string", - "pattern": "^\\d+$" - }, - "uint": { - "title": "hex encoded unsigned integer", - "type": "string", - "pattern": "^0x([1-9a-f]+[0-9a-f]*|0)$" - }, - "uint256": { - "title": "hex encoded unsigned integer", - "type": "string", - "pattern": "^0x[0-9a-f]{64}$" - }, - "hash16": { - "title": "16 byte hex value", - "type": "string", - "pattern": "^0x[0-9a-f]{32}$" - }, - "hash32": { - "title": "32 byte hex value", - "type": "string", - "pattern": "^0x[0-9a-f]{64}$" - }, - "null": { - "title": "null value", - "type": "null", - "pattern": "null" - }, - "subcall": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["CALL", "CREATE"], - "description": "The type of action (CALL for function calls or CREATE for contract creation)." - }, - "from": { - "$ref": "#/components/schemas/address", - "description": "The address of the sender." - }, - "to": { - "$ref": "#/components/schemas/address", - "description": "The address of the receiver. In the case of a contract creation, this might be null." - }, - "value": { - "$ref": "#/components/schemas/uint", - "description": "The value transferred with the call, in hexadecimal." - }, - "gas": { - "$ref": "#/components/schemas/uint", - "description": "The gas provided for the call, in hexadecimal." - }, - "gasUsed": { - "$ref": "#/components/schemas/uint", - "description": "The gas used during the call, in hexadecimal." - }, - "input": { - "$ref": "#/components/schemas/bytes", - "description": "The input data sent with the call." - }, - "output": { - "$ref": "#/components/schemas/bytes", - "description": "The output data returned by the call." - }, - "error": { - "type": "string", - "description": "Error encountered during the call, if any." - }, - "revertReason": { - "type": "string", - "description": "Solidity revert reason, if applicable." - } - }, - "required": ["from", "gas", "input"] - }, - "Block": { - "title": "Block object", - "type": "object", - "required": [ - "parentHash", - "sha3Uncles", - "miner", - "stateRoot", - "transactionsRoot", - "receiptsRoot", - "logsBloom", - "number", - "gasLimit", - "gasUsed", - "timestamp", - "extraData", - "mixHash", - "nonce", - "size", - "transactions", - "uncles", - "withdrawals", - "withdrawalsRoot" - ], - "properties": { - "parentHash": { - "title": "Parent block hash", - "$ref": "#/components/schemas/hash32" - }, - "sha3Uncles": { - "title": "Ommers hash", - "$ref": "#/components/schemas/hash32" - }, - "miner": { - "title": "Coinbase", - "$ref": "#/components/schemas/address" - }, - "stateRoot": { - "title": "State root", - "$ref": "#/components/schemas/hash32" - }, - "transactionsRoot": { - "title": "Transactions root", - "$ref": "#/components/schemas/hash32" - }, - "receiptsRoot": { - "title": "Receipts root", - "$ref": "#/components/schemas/hash32" - }, - "logsBloom": { - "title": "Bloom filter", - "$ref": "#/components/schemas/bytes256" - }, - "difficulty": { - "title": "Difficulty", - "$ref": "#/components/schemas/bytes" - }, - "number": { - "title": "Number", - "$ref": "#/components/schemas/uint" - }, - "gasLimit": { - "title": "Gas limit", - "$ref": "#/components/schemas/uint" - }, - "gasUsed": { - "title": "Gas used", - "$ref": "#/components/schemas/uint" - }, - "timestamp": { - "title": "Timestamp", - "$ref": "#/components/schemas/uint" - }, - "extraData": { - "title": "Extra data", - "$ref": "#/components/schemas/bytes" - }, - "mixHash": { - "title": "Mix hash", - "$ref": "#/components/schemas/hash32" - }, - "nonce": { - "title": "Nonce", - "$ref": "#/components/schemas/bytes8" - }, - "totalDifficulty": { - "title": "Total difficult", - "$ref": "#/components/schemas/uint" - }, - "baseFeePerGas": { - "title": "Base fee per gas", - "$ref": "#/components/schemas/uint" - }, - "size": { - "title": "Block size", - "$ref": "#/components/schemas/uint" - }, - "transactions": { - "anyOf": [ - { - "title": "Transaction hashes", - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - }, - { - "title": "Full transactions", - "type": "array", - "items": { - "$ref": "#/components/schemas/TransactionSigned" - } - } - ] - }, - "uncles": { - "title": "Uncles", - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - }, - "withdrawals": { - "title": "Withdrawals", - "type": "array", - "default": [] - }, - "withdrawalsRoot": { - "title": "Withdrawals root", - "default": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - } - }, - "BlockTag": { - "type": "string", - "enum": ["earliest", "latest", "pending", "finalized", "safe"], - "description": "`earliest`: The lowest numbered block the client has available; `latest` | `pending` | 'finalized' | 'safe' : The most recent block." - }, - "BlockNumberOrTag": { - "title": "Block number or tag", - "oneOf": [ - { - "title": "Block number", - "$ref": "#/components/schemas/uint" - }, - { - "title": "Block tag", - "$ref": "#/components/schemas/BlockTag" - } - ] - }, - "BlockNumberOrTagOrHash": { - "title": "Block number or tag", - "oneOf": [ - { - "title": "Block number", - "$ref": "#/components/schemas/uint" - }, - { - "title": "Block tag", - "$ref": "#/components/schemas/BlockTag" - }, - { - "title": "Block hash", - "$ref": "#/components/schemas/hash32" - } - ] - }, - "CallTracerConfig": { - "title": "CallTracer config", - "type": "object", - "properties": { - "onlyTopCall": { - "type": "boolean" - } - } - }, - "OpcodeLoggerConfig": { - "title": "OpcodeLogger config", - "type": "object", - "properties": { - "enableMemory": { - "type": "boolean" - }, - "disableStack": { - "type": "boolean" - }, - "disableStorage": { - "type": "boolean" - } - } - }, - "TracerConfig": { - "title": "Tracer config", - "type": "object", - "oneOf": [ - { - "$ref": "#/components/schemas/CallTracerConfig" - }, - { - "$ref": "#/components/schemas/OpcodeLoggerConfig" - } - ] - }, - "TracerType": { - "title": "Tracer type", - "type": "string", - "enum": ["callTracer", "opcodeLogger"] - }, - "TracerConfigWrapper": { - "title": "Tracer config wrapper", - "type": "object", - "properties": { - "tracer": { - "$ref": "#/components/schemas/TracerType" - }, - "tracerConfig": { - "$ref": "#/components/schemas/TracerConfig" - } - } - }, - "BlockTracerType": { - "title": "Block tracer type", - "type": "string", - "enum": ["callTracer", "prestateTracer"] - }, - "TransactionWithSender": { - "title": "Transaction object with sender", - "type": "object", - "allOf": [ - { - "required": ["from"], - "properties": { - "from": { - "title": "from", - "$ref": "#/components/schemas/address" - } - } - }, - { - "$ref": "#/components/schemas/TransactionUnsigned" - } - ] - }, - "Transaction1559Unsigned": { - "type": "object", - "title": "EIP-1559 transaction.", - "required": [ - "type", - "nonce", - "gas", - "value", - "input", - "maxFeePerGas", - "maxPriorityFeePerGas", - "chainId", - "accessList" - ], - "properties": { - "type": { - "title": "type", - "$ref": "#/components/schemas/byte" - }, - "nonce": { - "title": "nonce", - "$ref": "#/components/schemas/uint" - }, - "to": { - "title": "to address", - "$ref": "#/components/schemas/address" - }, - "gas": { - "title": "gas limit", - "$ref": "#/components/schemas/uint" - }, - "value": { - "title": "value", - "$ref": "#/components/schemas/uint" - }, - "input": { - "title": "input data", - "$ref": "#/components/schemas/bytes" - }, - "maxPriorityFeePerGas": { - "title": "max priority fee per gas", - "description": "Maximum fee per gas the sender is willing to pay to miners in wei", - "$ref": "#/components/schemas/uint" - }, - "maxFeePerGas": { - "title": "max fee per gas", - "description": "The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei", - "$ref": "#/components/schemas/uint" - }, - "accessList": { - "title": "accessList", - "type": "array" - }, - "chainId": { - "title": "chainId", - "description": "Chain ID that this transaction is valid on.", - "$ref": "#/components/schemas/uint" - } - } - }, - "TransactionLegacyUnsigned": { - "type": "object", - "title": "Legacy transaction.", - "required": ["type", "nonce", "gas", "value", "input", "gasPrice"], - "properties": { - "type": { - "title": "type", - "$ref": "#/components/schemas/byte" - }, - "nonce": { - "title": "nonce", - "$ref": "#/components/schemas/uint" - }, - "to": { - "title": "to address", - "$ref": "#/components/schemas/address" - }, - "gas": { - "title": "gas limit", - "$ref": "#/components/schemas/uint" - }, - "value": { - "title": "value", - "$ref": "#/components/schemas/uint" - }, - "input": { - "title": "input data", - "$ref": "#/components/schemas/bytes" - }, - "gasPrice": { - "title": "gas price", - "description": "The gas price willing to be paid by the sender in wei", - "$ref": "#/components/schemas/uint" - }, - "chainId": { - "title": "chainId", - "description": "Chain ID that this transaction is valid on.", - "$ref": "#/components/schemas/uint" - } - } - }, - "Transaction1559Signed": { - "title": "Signed 1559 Transaction", - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/Transaction1559Unsigned" - }, - { - "title": "EIP-1559 transaction signature properties.", - "required": ["yParity", "r", "s", "v"], - "properties": { - "yParity": { - "title": "yParity", - "description": "The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.", - "oneOf": [ - { - "$ref": "#/components/schemas/byte" - }, - { - "$ref": "#/components/schemas/null" - } - ] - }, - "r": { - "title": "r", - "$ref": "#/components/schemas/uint" - }, - "s": { - "title": "s", - "$ref": "#/components/schemas/uint" - }, - "v": { - "title": "v", - "description": "For backwards compatibility, `v` is optionally provided as an alternative to `yParity`. This field is DEPRECATED and all use of it should migrate to `yParity`.", - "oneOf": [ - { - "$ref": "#/components/schemas/byte" - }, - { - "$ref": "#/components/schemas/null" - } - ] - } - } - } - ] - }, - "TransactionUnsigned": { - "oneOf": [ - { - "$ref": "#/components/schemas/Transaction1559Unsigned" - }, - { - "$ref": "#/components/schemas/TransactionLegacyUnsigned" - } - ] - }, - "TransactionLegacySigned": { - "title": "Signed Legacy Transaction", - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/TransactionLegacyUnsigned" - }, - { - "title": "Legacy transaction signature properties.", - "required": ["v", "r", "s"], - "properties": { - "v": { - "title": "v", - "oneOf": [ - { - "$ref": "#/components/schemas/uint" - }, - { - "$ref": "#/components/schemas/null" - } - ] - }, - "r": { - "title": "r", - "$ref": "#/components/schemas/uint" - }, - "s": { - "title": "s", - "$ref": "#/components/schemas/uint" - } - } - } - ] - }, - "TransactionSigned": { - "anyOf": [ - { - "$ref": "#/components/schemas/Transaction1559Signed" - }, - { - "$ref": "#/components/schemas/TransactionLegacySigned" - } - ] - }, - "TransactionInfo": { - "type": "object", - "title": "Transaction information", - "allOf": [ - { - "title": "Contextual information", - "required": ["blockHash", "blockNumber", "from", "hash", "transactionIndex"], - "properties": { - "blockHash": { - "title": "block hash", - "$ref": "#/components/schemas/hash32" - }, - "blockNumber": { - "title": "block number", - "$ref": "#/components/schemas/uint" - }, - "from": { - "title": "from address", - "$ref": "#/components/schemas/address" - }, - "hash": { - "title": "transaction hash", - "$ref": "#/components/schemas/hash32" - }, - "transactionIndex": { - "title": "transaction index", - "$ref": "#/components/schemas/uint" - } - } - }, - { - "$ref": "#/components/schemas/TransactionSigned" - } - ] - }, - "Log": { - "title": "log", - "type": "object", - "required": ["transactionHash"], - "properties": { - "removed": { - "title": "removed", - "type": "boolean" - }, - "logIndex": { - "title": "log index", - "$ref": "#/components/schemas/uint" - }, - "transactionIndex": { - "title": "transaction index", - "$ref": "#/components/schemas/uint" - }, - "transactionHash": { - "title": "transaction hash", - "$ref": "#/components/schemas/hash32" - }, - "blockHash": { - "title": "block hash", - "$ref": "#/components/schemas/hash32" - }, - "blockNumber": { - "title": "block number", - "$ref": "#/components/schemas/uint" - }, - "address": { - "title": "address", - "$ref": "#/components/schemas/address" - }, - "data": { - "title": "data", - "$ref": "#/components/schemas/bytes" - }, - "topics": { - "title": "topics", - "type": "array", - "items": { - "$ref": "#/components/schemas/bytes32" - } - } - } - }, - "ReceiptInfo": { - "type": "object", - "title": "Receipt info", - "required": [ - "blockHash", - "blockNumber", - "from", - "cumulativeGasUsed", - "gasUsed", - "logs", - "logsBloom", - "transactionHash", - "transactionIndex", - "effectiveGasPrice" - ], - "properties": { - "transactionHash": { - "title": "transaction hash", - "$ref": "#/components/schemas/hash32" - }, - "transactionIndex": { - "title": "transaction index", - "$ref": "#/components/schemas/uint" - }, - "blockHash": { - "title": "block hash", - "$ref": "#/components/schemas/hash32" - }, - "blockNumber": { - "title": "block number", - "$ref": "#/components/schemas/uint" - }, - "from": { - "title": "from", - "$ref": "#/components/schemas/address" - }, - "to": { - "title": "to", - "description": "Address of the receiver or null in a contract creation transaction.", - "$ref": "#/components/schemas/address" - }, - "cumulativeGasUsed": { - "title": "cumulative gas used", - "description": "The sum of gas used by this transaction and all preceding transactions in the same block.", - "$ref": "#/components/schemas/uint" - }, - "gasUsed": { - "title": "gas used", - "description": "The amount of gas used for this specific transaction alone.", - "$ref": "#/components/schemas/uint" - }, - "contractAddress": { - "title": "contract address", - "description": "The contract address created, if the transaction was a contract creation, otherwise null.", - "$ref": "#/components/schemas/address" - }, - "logs": { - "title": "logs", - "type": "array", - "items": { - "$ref": "#/components/schemas/Log" - } - }, - "logsBloom": { - "title": "logs bloom", - "$ref": "#/components/schemas/bytes256" - }, - "root": { - "title": "state root", - "description": "The post-transaction state root. Only specified for transactions included before the Byzantium upgrade.", - "$ref": "#/components/schemas/bytes32" - }, - "status": { - "title": "status", - "description": "Either 1 (success) or 0 (failure). Only specified for transactions included after the Byzantium upgrade.", - "$ref": "#/components/schemas/uint" - }, - "effectiveGasPrice": { - "title": "effective gas price", - "description": "The actual value per gas deducted from the senders account. Before EIP-1559, this is equal to the transaction's gas price. After, it is equal to baseFeePerGas + min(maxFeePerGas - baseFeePerGas, maxPriorityFeePerGas).", - "$ref": "#/components/schemas/uint" - } - } - }, - "FilterResults": { - "title": "Filter results", - "oneOf": [ - { - "title": "new block hashes", - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - }, - { - "title": "new transaction hashes", - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - }, - { - "title": "new logs", - "type": "array", - "items": { - "$ref": "#/components/schemas/Log" - } - } - ] - }, - "Filter": { - "title": "filter", - "type": "object", - "properties": { - "fromBlock": { - "title": "from block", - "$ref": "#/components/schemas/uint" - }, - "toBlock": { - "title": "to block", - "$ref": "#/components/schemas/uint" - }, - "blockHash": { - "title": "Block hash", - "$ref": "#/components/schemas/hash32" - }, - "address": { - "title": "Address(es)", - "oneOf": [ - { - "title": "Address", - "$ref": "#/components/schemas/address" - }, - { - "title": "Addresses", - "$ref": "#/components/schemas/addresses" - } - ] - }, - "topics": { - "title": "Topics", - "$ref": "#/components/schemas/FilterTopics" - } - } - }, - "FilterTopics": { - "title": "Filter Topics", - "type": "array", - "items": { - "$ref": "#/components/schemas/FilterTopic" - } - }, - "FilterTopic": { - "title": "Filter Topic List Entry", - "oneOf": [ - { - "title": "Any Topic Match", - "type": "null" - }, - { - "title": "Single Topic Match", - "$ref": "#/components/schemas/bytes32" - }, - { - "title": "Multiple Topic Match", - "type": "array", - "items": { - "$ref": "#/components/schemas/bytes32" - } - } - ] - }, - "LogFilter": { - "title": "log filter", - "type": "object", - "properties": { - "fromBlock": { - "title": "from block", - "$ref": "#/components/schemas/uint" - }, - "toBlock": { - "title": "to block", - "$ref": "#/components/schemas/uint" - }, - "address": { - "title": "Address", - "$ref": "#/components/schemas/address" - }, - "topics": { - "title": "Topics", - "$ref": "#/components/schemas/FilterTopics" - } - } - }, - "unsupportedError": { - "name": "Unsupported Error", - "schema": { - "type": "object", - "properties": { - "code": { - "title": "Error code", - "type": "number", - "pattern": "-32601" - }, - "message": { - "title": "Error message", - "type": "string", - "pattern": "Unsupported JSON-RPC method" - }, - "name": { - "title": "Error name", - "type": "string", - "pattern": "Method not found" - } - } - } - }, - "callframe": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["CALL", "CREATE"], - "description": "The type of action (CALL for function calls or CREATE for contract creation)." - }, - "from": { - "$ref": "#/components/schemas/address", - "description": "The address of the sender." - }, - "to": { - "$ref": "#/components/schemas/address", - "description": "The address of the receiver. In the case of a contract creation, this might be null." - }, - "value": { - "$ref": "#/components/schemas/uint", - "description": "The value transferred with the call, in hexadecimal." - }, - "gas": { - "$ref": "#/components/schemas/uint", - "description": "The gas provided for the call, in hexadecimal." - }, - "gasUsed": { - "$ref": "#/components/schemas/uint", - "description": "The gas used during the call, in hexadecimal." - }, - "input": { - "$ref": "#/components/schemas/bytes", - "description": "The input data sent with the call." - }, - "output": { - "$ref": "#/components/schemas/bytes", - "description": "The output data returned by the call." - }, - "error": { - "type": "string", - "description": "Error encountered during the call, if any." - }, - "revertReason": { - "type": "string", - "description": "Solidity revert reason, if applicable." - }, - "calls": { - "type": "array", - "items": { - "$ref": "#/components/schemas/subcall" - }, - "description": "Sub-calls made during this call frame." - } - }, - "required": ["from", "gas", "input"] - } - } - } -} From 2a6669115bcce1e7348ca8d36e0fbd7569987b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Tue, 3 Jun 2025 15:14:08 +0200 Subject: [PATCH 03/17] chore: refactor utilities with new helper classes (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/cli.js | 6 +- scripts/openrpc-json-updater/config.js | 35 +- .../openrpc-json-updater/operations/merge.js | 328 +++++++++++++++--- .../operations/prepare.js | 2 +- .../openrpc-json-updater/operations/report.js | 9 +- .../openrpc-json-updater/utils/file.utils.js | 205 +++++++++-- .../openrpc-json-updater/utils/merge.utils.js | 294 ++++++++++++++-- .../utils/openrpc.utils.js | 120 ++++--- 8 files changed, 808 insertions(+), 191 deletions(-) diff --git a/scripts/openrpc-json-updater/cli.js b/scripts/openrpc-json-updater/cli.js index 994a543c2e..24f6e0d4ce 100644 --- a/scripts/openrpc-json-updater/cli.js +++ b/scripts/openrpc-json-updater/cli.js @@ -1,7 +1,7 @@ -import { readJson, writeJson } from './utils/file.utils.js'; -import { mergeDocuments } from './operations/merge.js'; +import { compareIgnoringFormatting, prepareDocuments } from './operations/prepare.js'; import { generateReport } from './operations/report.js'; -import { prepareDocuments, compareIgnoringFormatting } from './operations/prepare.js'; +import { mergeDocuments } from './operations/merge.js'; +import { readJson, writeJson } from './utils/file.utils.js'; const originalFilePath = './original-openrpc.json'; const modifiedFilePath = '../../docs/openrpc.json'; diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js index b97d47b739..4e74a31bf3 100644 --- a/scripts/openrpc-json-updater/config.js +++ b/scripts/openrpc-json-updater/config.js @@ -1,7 +1,7 @@ export const SKIPPED_KEYS = [ "examples", "baseFeePerBlobGas", - "blobGasUsedRatio" + "blobGasUsedRatio", ]; export const CUSTOM_FIELDS = [ @@ -48,7 +48,7 @@ export const CUSTOM_FIELDS = [ ]; export const DISCARDED_METHODS = [ - "engine_*" + "engine_*", ]; export const NOT_IMPLEMENTED_METHODS = [ @@ -56,7 +56,7 @@ export const NOT_IMPLEMENTED_METHODS = [ "debug_getRawBlock", "debug_getRawHeader", "debug_getRawReceipts", - "debug_getRawTransaction" + "debug_getRawTransaction", ]; export const SKIPPED_METHODS = [ @@ -112,24 +112,25 @@ export function shouldSkipPath(path) { export function getSkippedMethodCategory(methodName) { if (!methodName) return null; - - for (const pattern of DISCARDED_METHODS) { - if (pattern === methodName) return 'discarded'; - + + const matchesPattern = (pattern, method) => { + if (pattern === method) return true; + if (pattern.endsWith('*')) { const prefix = pattern.slice(0, -1); - if (methodName.startsWith(prefix)) return 'discarded'; + return method.startsWith(prefix); } + + return false; + }; + + if (DISCARDED_METHODS.some(pattern => matchesPattern(pattern, methodName))) { + return 'discarded'; } - - for (const pattern of NOT_IMPLEMENTED_METHODS) { - if (pattern === methodName) return 'not implemented'; - - if (pattern.endsWith('*')) { - const prefix = pattern.slice(0, -1); - if (methodName.startsWith(prefix)) return 'not implemented'; - } + + if (NOT_IMPLEMENTED_METHODS.some(pattern => matchesPattern(pattern, methodName))) { + return 'not implemented'; } - + return null; } diff --git a/scripts/openrpc-json-updater/operations/merge.js b/scripts/openrpc-json-updater/operations/merge.js index 1abd97ceef..57918d1149 100644 --- a/scripts/openrpc-json-updater/operations/merge.js +++ b/scripts/openrpc-json-updater/operations/merge.js @@ -1,16 +1,16 @@ +import { shouldSkipKey, shouldSkipMethod, shouldSkipPath } from '../config.js'; import { getMethodMap, getDifferingKeys } from '../utils/openrpc.utils.js'; -import {shouldSkipPath, shouldSkipKey, shouldSkipMethod} from '../config.js'; import { - setNestedValue, + filterSkippedMethods, + findRefPaths, getNestedValue, + getObjectByPath, + handleRefField, + handleRefFieldsWithOriginal, hasNestedPath, removeSkippedKeys, - handleRefField, - getObjectByPath, + setNestedValue, setObjectByPath, - findRefPaths, - handleRefFieldsWithOriginal, - filterSkippedMethods } from '../utils/merge.utils.js'; class MergeDocuments { @@ -86,22 +86,79 @@ class MergeDocuments { const refPaths = findRefPaths(origMethod); for (const { path } of refPaths) { - const targetObj = path ? getObjectByPath(modMethod, path) : modMethod; - const origObj = path ? getObjectByPath(origMethod, path) : origMethod; - - if (targetObj && typeof targetObj === 'object' && origObj && typeof origObj === 'object') { - if (path) { - setObjectByPath(modMethod, path, JSON.parse(JSON.stringify(origObj))); - } else { - for (const key in modMethod) { - delete modMethod[key]; - } - for (const key in origObj) { - modMethod[key] = origObj[key]; - } - } - } + this.replaceObjectAtPath(origMethod, modMethod, path); + } + } + + /** + * Replaces an object at a specific path with the original object + * @param {Object} origMethod - The original method + * @param {Object} modMethod - The modified method + * @param {string} path - The path to the object to replace + * @private + */ + replaceObjectAtPath(origMethod, modMethod, path) { + const targetObj = this.getObjectAtPath(modMethod, path); + const origObj = this.getObjectAtPath(origMethod, path); + + if (!this.areValidObjects(targetObj, origObj)) { + return; } + + if (path) { + setObjectByPath(modMethod, path, this.deepClone(origObj)); + } else { + this.replaceRootObject(modMethod, origObj); + } + } + + /** + * Gets an object at the specified path or returns the root object if no path + * @param {Object} obj - The object to navigate + * @param {string} path - The path to navigate to + * @returns {*} The object at the path or the root object + * @private + */ + getObjectAtPath(obj, path) { + return path ? getObjectByPath(obj, path) : obj; + } + + /** + * Validates that both objects are valid objects + * @param {*} targetObj - The target object + * @param {*} origObj - The original object + * @returns {boolean} True if both are valid objects + * @private + */ + areValidObjects(targetObj, origObj) { + return targetObj && + typeof targetObj === 'object' && + origObj && + typeof origObj === 'object'; + } + + /** + * Creates a deep clone of an object + * @param {*} obj - The object to clone + * @returns {*} A deep clone of the object + * @private + */ + deepClone(obj) { + return JSON.parse(JSON.stringify(obj)); + } + + /** + * Replaces the root object by clearing the target and copying from source + * @param {Object} targetObj - The object to clear and replace + * @param {Object} sourceObj - The source object to copy from + * @private + */ + replaceRootObject(targetObj, sourceObj) { + Object.keys(targetObj).forEach(key => { + delete targetObj[key]; + }); + + Object.assign(targetObj, sourceObj); } /** @@ -164,30 +221,103 @@ class MergeDocuments { * @param {Object} filteredModified - The filtered modified document */ mergeComponents(filteredOriginal, filteredModified) { - if (!filteredOriginal.components || typeof filteredOriginal.components !== 'object') { + if (!this.hasValidComponents(filteredOriginal)) { return; } - if (!filteredModified.components) { - filteredModified.components = {}; + this.ensureComponentsExist(filteredModified); + + const originalComponents = filteredOriginal.components; + const modifiedComponents = filteredModified.components; + + Object.keys(originalComponents).forEach(sectionName => { + this.mergeSectionComponents(originalComponents[sectionName], modifiedComponents, sectionName); + }); + } + + /** + * Validates if the document has valid components + * @param {Object} document - The document to validate + * @returns {boolean} True if document has valid components + * @private + */ + hasValidComponents(document) { + return document?.components && typeof document.components === 'object'; + } + + /** + * Ensures the components object exists in the target document + * @param {Object} document - The document to ensure components exist in + * @private + */ + ensureComponentsExist(document) { + if (!document.components) { + document.components = {}; } + } - for (const section in filteredOriginal.components) { - if (!filteredModified.components[section]) { - filteredModified.components[section] = {}; - } + /** + * Merges components from a specific section + * @param {Object} originalSection - The original section components + * @param {Object} modifiedComponents - The modified document's components object + * @param {string} sectionName - The name of the section being merged + * @private + */ + mergeSectionComponents(originalSection, modifiedComponents, sectionName) { + if (!originalSection || typeof originalSection !== 'object') { + return; + } - for (const key in filteredOriginal.components[section]) { - if (shouldSkipKey(key)) continue; + this.ensureSectionExists(modifiedComponents, sectionName); - if (!filteredModified.components[section][key]) { - filteredModified.components[section][key] = - removeSkippedKeys(filteredOriginal.components[section][key]); - } - } + const validKeys = this.getValidKeysFromSection(originalSection); + + validKeys.forEach(key => { + this.mergeComponentKey(originalSection[key], modifiedComponents[sectionName], key); + }); + } + + /** + * Ensures a specific section exists in the components object + * @param {Object} components - The components object + * @param {string} sectionName - The section name to ensure exists + * @private + */ + ensureSectionExists(components, sectionName) { + if (!components[sectionName]) { + components[sectionName] = {}; + } + } + + /** + * Gets valid keys from a section (excludes skipped keys) + * @param {Object} section - The section to get keys from + * @returns {string[]} Array of valid key names + * @private + */ + getValidKeysFromSection(section) { + return Object.keys(section).filter(key => !shouldSkipKey(key)); + } + + /** + * Merges a specific component key if it doesn't already exist + * @param {*} originalValue - The original component value + * @param {Object} targetSection - The target section to merge into + * @param {string} key - The component key name + * @private + */ + mergeComponentKey(originalValue, targetSection, key) { + if (!targetSection[key]) { + targetSection[key] = removeSkippedKeys(originalValue); } } + /** + * Processes a document to handle $ref fields and remove skipped keys + * @param {Object} document - The document to process + * @param {Object} originalDocument - The original document + * @returns {Object} - The processed document + */ /** * Processes a document to handle $ref fields and remove skipped keys * @param {Object} document - The document to process @@ -195,39 +325,127 @@ class MergeDocuments { * @returns {Object} - The processed document */ processDocument(document, originalDocument) { - if (!document || !document.methods || !Array.isArray(document.methods)) { + if (!this.isValidDocument(document)) { return document; } - const result = JSON.parse(JSON.stringify(document)); + const result = this.cloneDocument(document); - // Filter methods that should be skipped - result.methods = result.methods.filter(method => { + this.filterAndCleanMethods(result); + + if (originalDocument) { + return this.processWithOriginalDocument(result, originalDocument); + } + + return handleRefField(result); + } + + /** + * Validates if the document has the required structure + * @param {Object} document - The document to validate + * @returns {boolean} True if document is valid + * @private + */ + isValidDocument(document) { + return document && + document.methods && + Array.isArray(document.methods); + } + + /** + * Creates a deep clone of the document + * @param {Object} document - The document to clone + * @returns {Object} A deep clone of the document + * @private + */ + cloneDocument(document) { + return JSON.parse(JSON.stringify(document)); + } + + /** + * Filters out skipped methods and removes skipped keys from remaining methods + * @param {Object} document - The document to process + * @private + */ + filterAndCleanMethods(document) { + document.methods = this.filterValidMethods(document.methods); + document.methods = this.removeSkippedKeysFromMethods(document.methods); + } + + /** + * Filters methods that should not be skipped + * @param {Array} methods - Array of methods to filter + * @returns {Array} Filtered array of methods + * @private + */ + filterValidMethods(methods) { + return methods.filter(method => { const methodName = method?.name; if (!methodName) return true; return !shouldSkipMethod(methodName); }); + } - // Remove skipped keys - result.methods = result.methods.map(method => removeSkippedKeys(method)); + /** + * Removes skipped keys from all methods + * @param {Array} methods - Array of methods to clean + * @returns {Array} Array of methods with skipped keys removed + * @private + */ + removeSkippedKeysFromMethods(methods) { + return methods.map(method => removeSkippedKeys(method)); + } - if (originalDocument) { - if (result.methods && originalDocument.methods) { - const origMethodMap = getMethodMap(originalDocument); - result.methods = result.methods.map(method => { - const origMethod = origMethodMap.get(method.name); - return origMethod ? handleRefFieldsWithOriginal(method, origMethod) : method; - }); - } + /** + * Processes the document when an original document is available + * @param {Object} document - The document to process + * @param {Object} originalDocument - The original document for reference + * @returns {Object} The processed document + * @private + */ + processWithOriginalDocument(document, originalDocument) { + this.processMethodsWithOriginal(document, originalDocument); + this.processComponentsWithOriginal(document, originalDocument); + return document; + } - if (result.components && originalDocument.components) { - result.components = handleRefFieldsWithOriginal(result.components, originalDocument.components, true); - } + /** + * Processes methods with reference to the original document + * @param {Object} document - The document to process + * @param {Object} originalDocument - The original document for reference + * @private + */ + processMethodsWithOriginal(document, originalDocument) { + if (!document.methods || !originalDocument.methods) { + return; + } - return result; + const origMethodMap = getMethodMap(originalDocument); + + document.methods = document.methods.map(method => { + const origMethod = origMethodMap.get(method.name); + return origMethod + ? handleRefFieldsWithOriginal(method, origMethod) + : method; + }); + } + + /** + * Processes components with reference to the original document + * @param {Object} document - The document to process + * @param {Object} originalDocument - The original document for reference + * @private + */ + processComponentsWithOriginal(document, originalDocument) { + if (!document.components || !originalDocument.components) { + return; } - return handleRefField(result); + document.components = handleRefFieldsWithOriginal( + document.components, + originalDocument.components, + true + ); } } diff --git a/scripts/openrpc-json-updater/operations/prepare.js b/scripts/openrpc-json-updater/operations/prepare.js index 17ecdd3721..853eca721b 100644 --- a/scripts/openrpc-json-updater/operations/prepare.js +++ b/scripts/openrpc-json-updater/operations/prepare.js @@ -3,7 +3,7 @@ import diff from 'deep-diff'; export function prepareDocuments(originalJson, modifiedJson) { return { normalizedOriginal: normalizeDocument(originalJson), - normalizedModified: normalizeDocument(modifiedJson) + normalizedModified: normalizeDocument(modifiedJson), }; } diff --git a/scripts/openrpc-json-updater/operations/report.js b/scripts/openrpc-json-updater/operations/report.js index f6fa6438c6..de1ef4bdc8 100644 --- a/scripts/openrpc-json-updater/operations/report.js +++ b/scripts/openrpc-json-updater/operations/report.js @@ -3,7 +3,7 @@ import { getSkippedMethodCategory } from '../config.js'; export async function generateReport( originalJson, - modifiedJson + modifiedJson, ) { const originalMethods = getMethodMap(originalJson); const modifiedMethods = getMethodMap(modifiedJson); @@ -12,10 +12,9 @@ export async function generateReport( for (const name of originalMethods.keys()) { if (!modifiedMethods.has(name)) { const category = getSkippedMethodCategory(name); - missingMethods.push({ missingMethod: name, - status: category ? `${category}` : 'a new method' + status: category ? `${category}` : 'a new method', }); } } @@ -31,7 +30,7 @@ export async function generateReport( changedMethods.push({ method: name, valueDiscrepancies: groupPaths(valueDiscrepancies, 3), - customFields: groupPaths(customFields, 3) + customFields: groupPaths(customFields, 3), }); } } @@ -43,7 +42,7 @@ export async function generateReport( if (missingMethods.length > 0) { console.log( - '\nMethods present in the original document but missing from the modified document:\n' + '\nMethods present in the original document but missing from the modified document:\n', ); console.table(missingMethods); diff --git a/scripts/openrpc-json-updater/utils/file.utils.js b/scripts/openrpc-json-updater/utils/file.utils.js index 5b48f50572..f55179bb1e 100644 --- a/scripts/openrpc-json-updater/utils/file.utils.js +++ b/scripts/openrpc-json-updater/utils/file.utils.js @@ -13,46 +13,187 @@ export function readJson(filePath) { } } +/** + * Formats a JSON object with custom indentation and compact array handling + * @param {*} obj - The object to format + * @param {string} indent - The indentation string (default: ' ') + * @param {number} level - The current nesting level (default: 0) + * @returns {string} Formatted JSON string + */ function formatJson(obj, indent = ' ', level = 0) { - const currentIndent = indent.repeat(level); - const nextIndent = indent.repeat(level + 1); - - if (obj === null || obj === undefined) { - return 'null'; + const formatter = new JsonFormatter(indent, level); + return formatter.format(obj); +} + +/** + * JSON formatter class that handles different data types and formatting rules + */ +class JsonFormatter { + constructor(indent, level) { + this.indent = indent; + this.level = level; + this.currentIndent = indent.repeat(level); + this.nextIndent = indent.repeat(level + 1); + } + + /** + * Main formatting method that delegates to specific type handlers + * @param {*} obj - The object to format + * @returns {string} Formatted string representation + */ + format(obj) { + if (this.isPrimitive(obj)) { + return this.formatPrimitive(obj); + } + + if (Array.isArray(obj)) { + return this.formatArray(obj); + } + + return this.formatObject(obj); + } + + /** + * Checks if a value is a primitive type + * @param {*} value - The value to check + * @returns {boolean} True if the value is primitive + */ + isPrimitive(value) { + return value === null || + value === undefined || + typeof value === 'number' || + typeof value === 'boolean' || + typeof value === 'string'; + } + + /** + * Formats primitive values (null, undefined, numbers, booleans, strings) + * @param {*} value - The primitive value to format + * @returns {string} Formatted primitive value + */ + formatPrimitive(value) { + if (value === null || value === undefined) { + return 'null'; + } + + if (typeof value === 'number' || typeof value === 'boolean') { + return String(value); + } + + if (typeof value === 'string') { + return JSON.stringify(value); + } + + return String(value); + } + + /** + * Formats arrays with compact or expanded layout based on content + * @param {Array} array - The array to format + * @returns {string} Formatted array string + */ + formatArray(array) { + if (array.length === 0) { + return '[]'; + } + + if (this.shouldUseCompactArrayFormat(array)) { + return this.formatCompactArray(array); + } + + return this.formatExpandedArray(array); } - - if (typeof obj === 'number' || typeof obj === 'boolean') { - return String(obj); + + /** + * Determines if an array should use compact formatting + * @param {Array} array - The array to check + * @returns {boolean} True if compact formatting should be used + */ + shouldUseCompactArrayFormat(array) { + const maxCompactLength = 8; + const maxStringLength = 40; + + return array.length <= maxCompactLength && + array.every(item => this.isCompactableItem(item, maxStringLength)); } - - if (typeof obj === 'string') { - return JSON.stringify(obj); + + /** + * Checks if an array item is suitable for compact formatting + * @param {*} item - The item to check + * @param {number} maxStringLength - Maximum string length for compact format + * @returns {boolean} True if item can be formatted compactly + */ + isCompactableItem(item, maxStringLength) { + return (typeof item === 'string' && item.length < maxStringLength) || + typeof item === 'number' || + typeof item === 'boolean'; + } + + /** + * Formats an array in compact single-line format + * @param {Array} array - The array to format + * @returns {string} Compact formatted array + */ + formatCompactArray(array) { + const items = array + .map(item => this.createChildFormatter().format(item)) + .join(', '); + return `[${items}]`; } - - if (Array.isArray(obj)) { - if (obj.length <= 8 && obj.every(item => - (typeof item === 'string' && item.length < 40) || - (typeof item === 'number') || - (typeof item === 'boolean'))) { - const items = obj.map(item => formatJson(item, indent)).join(', '); - return `[${items}]`; + + /** + * Formats an array in expanded multi-line format + * @param {Array} array - The array to format + * @returns {string} Expanded formatted array + */ + formatExpandedArray(array) { + const childFormatter = this.createChildFormatter(); + const items = array + .map(item => this.nextIndent + childFormatter.format(item)) + .join(',\n'); + + return `[\n${items}\n${this.currentIndent}]`; + } + + /** + * Formats objects with proper indentation and key-value pairs + * @param {Object} obj - The object to format + * @returns {string} Formatted object string + */ + formatObject(obj) { + const entries = Object.entries(obj); + + if (entries.length === 0) { + return '{}'; } - const items = obj.map(item => nextIndent + formatJson(item, indent, level + 1)).join(',\n'); - return items.length > 0 ? `[\n${items}\n${currentIndent}]` : '[]'; + const childFormatter = this.createChildFormatter(); + const props = entries + .map(([key, value]) => this.formatObjectProperty(key, value, childFormatter)) + .join(',\n'); + + return `{\n${props}\n${this.currentIndent}}`; + } + + /** + * Formats a single object property (key-value pair) + * @param {string} key - The property key + * @param {*} value - The property value + * @param {JsonFormatter} childFormatter - Formatter for the child value + * @returns {string} Formatted property string + */ + formatObjectProperty(key, value, childFormatter) { + const formattedValue = childFormatter.format(value); + return `${this.nextIndent}"${key}": ${formattedValue}`; } - - const entries = Object.entries(obj); - if (entries.length === 0) { - return '{}'; + + /** + * Creates a child formatter for the next nesting level + * @returns {JsonFormatter} A new formatter for child elements + */ + createChildFormatter() { + return new JsonFormatter(this.indent, this.level + 1); } - - const props = entries.map(([key, value]) => { - const formattedValue = formatJson(value, indent, level + 1); - return `${nextIndent}"${key}": ${formattedValue}`; - }).join(',\n'); - - return `{\n${props}\n${currentIndent}}`; } export function writeJson(filePath, data, originalContent) { diff --git a/scripts/openrpc-json-updater/utils/merge.utils.js b/scripts/openrpc-json-updater/utils/merge.utils.js index 99da5830b8..78104b6344 100644 --- a/scripts/openrpc-json-updater/utils/merge.utils.js +++ b/scripts/openrpc-json-updater/utils/merge.utils.js @@ -132,63 +132,292 @@ export function setObjectByPath(obj, path, value) { /** * Finds all paths in an object that contain $ref fields + * @param {*} obj - The object to search + * @param {string} currentPath - The current path (used internally for recursion) + * @param {Array} paths - The accumulated paths array (used internally for recursion) + * @returns {Array} Array of objects with path and ref properties */ export function findRefPaths(obj, currentPath = '', paths = []) { - if (!obj || typeof obj !== 'object') return paths; + const pathFinder = new RefPathFinder(); + return pathFinder.find(obj, currentPath, paths); +} - if (Array.isArray(obj)) { - for (let i = 0; i < obj.length; i++) { - findRefPaths(obj[i], currentPath ? `${currentPath}.${i}` : `${i}`, paths); +/** + * Class responsible for finding $ref paths in objects + */ +class RefPathFinder { + /** + * Main method to find all $ref paths in an object + * @param {*} obj - The object to search + * @param {string} currentPath - The current path + * @param {Array} paths - The accumulated paths array + * @returns {Array} Array of ref path objects + */ + find(obj, currentPath = '', paths = []) { + if (!this.isValidObject(obj)) { + return paths; } - } else { - if (obj['$ref'] !== undefined) { - paths.push({ - path: currentPath, - ref: obj['$ref'] - }); + + if (Array.isArray(obj)) { + this.processArray(obj, currentPath, paths); + } else { + this.processObject(obj, currentPath, paths); } - for (const key in obj) { - if (typeof obj[key] === 'object' && obj[key] !== null) { - findRefPaths(obj[key], currentPath ? `${currentPath}.${key}` : key, paths); - } + return paths; + } + + /** + * Validates if the object is suitable for processing + * @param {*} obj - The object to validate + * @returns {boolean} True if object can be processed + */ + isValidObject(obj) { + return obj && typeof obj === 'object'; + } + + /** + * Processes an array and recursively searches its elements + * @param {Array} array - The array to process + * @param {string} currentPath - The current path + * @param {Array} paths - The accumulated paths array + */ + processArray(array, currentPath, paths) { + array.forEach((item, index) => { + const itemPath = this.buildPath(currentPath, index); + this.find(item, itemPath, paths); + }); + } + + /** + * Processes an object and searches for $ref fields and nested objects + * @param {Object} obj - The object to process + * @param {string} currentPath - The current path + * @param {Array} paths - The accumulated paths array + */ + processObject(obj, currentPath, paths) { + if (this.hasRefField(obj)) { + this.addRefPath(currentPath, obj['$ref'], paths); } + + this.processObjectProperties(obj, currentPath, paths); + } + + /** + * Checks if an object has a $ref field + * @param {Object} obj - The object to check + * @returns {boolean} True if object has $ref field + */ + hasRefField(obj) { + return obj['$ref'] !== undefined; + } + + /** + * Adds a ref path to the paths array + * @param {string} path - The path to the ref + * @param {string} ref - The ref value + * @param {Array} paths - The paths array to add to + */ + addRefPath(path, ref, paths) { + paths.push({ + path, + ref + }); + } + + /** + * Processes all properties of an object recursively + * @param {Object} obj - The object whose properties to process + * @param {string} currentPath - The current path + * @param {Array} paths - The accumulated paths array + */ + processObjectProperties(obj, currentPath, paths) { + Object.keys(obj).forEach(key => { + const value = obj[key]; + if (this.shouldProcessProperty(value)) { + const propertyPath = this.buildPath(currentPath, key); + this.find(value, propertyPath, paths); + } + }); + } + + /** + * Determines if a property value should be processed recursively + * @param {*} value - The property value + * @returns {boolean} True if value should be processed + */ + shouldProcessProperty(value) { + return typeof value === 'object' && value !== null; } - return paths; + /** + * Builds a dot-notation path from current path and new segment + * @param {string} currentPath - The current path + * @param {string|number} segment - The new path segment + * @returns {string} The combined path + */ + buildPath(currentPath, segment) { + return currentPath ? `${currentPath}.${segment}` : String(segment); + } } /** * Handles $ref fields with an original object + * @param {*} obj - The object to process + * @param {*} origObj - The original object for reference + * @param {boolean} isComponent - Whether processing components + * @returns {*} The processed object */ export function handleRefFieldsWithOriginal(obj, origObj, isComponent = false) { - if (!obj || typeof obj !== 'object') return obj; - if (!origObj || typeof origObj !== 'object') return isComponent ? obj : handleRefField(obj); + const refHandler = new RefFieldHandler(isComponent); + return refHandler.handle(obj, origObj); +} - if (Array.isArray(obj)) { - return obj.map((item, index) => - index < origObj.length ? handleRefFieldsWithOriginal(item, origObj[index], isComponent) : (isComponent ? item : handleRefField(item)) - ); +/** + * Class responsible for handling $ref fields with original object references + */ +class RefFieldHandler { + constructor(isComponent = false) { + this.isComponent = isComponent; } - if (origObj['$ref'] !== undefined) { - return JSON.parse(JSON.stringify(origObj)); + /** + * Main method to handle ref fields + * @param {*} obj - The object to process + * @param {*} origObj - The original object for reference + * @returns {*} The processed object + */ + handle(obj, origObj) { + if (!this.isValidObject(obj)) { + return obj; + } + + if (!this.isValidObject(origObj)) { + return this.handleWithoutOriginal(obj); + } + + if (Array.isArray(obj)) { + return this.handleArray(obj, origObj); + } + + return this.handleObject(obj, origObj); } - if (obj['$ref'] !== undefined) { - if (isComponent) { - return JSON.parse(JSON.stringify(obj)); + /** + * Validates if an object is suitable for processing + * @param {*} obj - The object to validate + * @returns {boolean} True if object can be processed + */ + isValidObject(obj) { + return obj && typeof obj === 'object'; + } + + /** + * Handles processing when no valid original object is available + * @param {*} obj - The object to process + * @returns {*} The processed object + */ + handleWithoutOriginal(obj) { + return this.isComponent ? obj : handleRefField(obj); + } + + /** + * Handles array processing with original array reference + * @param {Array} array - The array to process + * @param {Array} origArray - The original array for reference + * @returns {Array} The processed array + */ + handleArray(array, origArray) { + return array.map((item, index) => { + const origItem = this.getArrayItemSafely(origArray, index); + return this.handle(item, origItem); + }); + } + + /** + * Safely gets an item from an array at the specified index + * @param {Array} array - The array to get item from + * @param {number} index - The index to get + * @returns {*} The item at index or undefined + */ + getArrayItemSafely(array, index) { + return Array.isArray(array) && index < array.length ? array[index] : undefined; + } + + /** + * Handles object processing with original object reference + * @param {Object} obj - The object to process + * @param {Object} origObj - The original object for reference + * @returns {Object} The processed object + */ + handleObject(obj, origObj) { + if (this.hasRefField(origObj)) { + return this.cloneObject(origObj); } - return { '$ref': obj['$ref'] }; + + if (this.hasRefField(obj)) { + return this.handleObjectWithRef(obj); + } + + return this.processObjectProperties(obj, origObj); } - const newObj = {}; - for (const key in obj) { - const origValue = origObj[key]; - newObj[key] = handleRefFieldsWithOriginal(obj[key], origValue, isComponent); + /** + * Checks if an object has a $ref field + * @param {Object} obj - The object to check + * @returns {boolean} True if object has $ref field + */ + hasRefField(obj) { + return obj && obj['$ref'] !== undefined; + } + + /** + * Creates a deep clone of an object + * @param {Object} obj - The object to clone + * @returns {Object} A deep clone of the object + */ + cloneObject(obj) { + return JSON.parse(JSON.stringify(obj)); + } + + /** + * Handles an object that contains a $ref field + * @param {Object} obj - The object with $ref field + * @returns {Object} The processed ref object + */ + handleObjectWithRef(obj) { + if (this.isComponent) { + return this.cloneObject(obj); + } + return this.createRefOnlyObject(obj['$ref']); } - return newObj; + /** + * Creates an object containing only the $ref field + * @param {string} refValue - The $ref value + * @returns {Object} Object with only $ref field + */ + createRefOnlyObject(refValue) { + return { '$ref': refValue }; + } + + /** + * Processes all properties of an object recursively + * @param {Object} obj - The object to process + * @param {Object} origObj - The original object for reference + * @returns {Object} The processed object with all properties handled + */ + processObjectProperties(obj, origObj) { + const newObj = {}; + + Object.keys(obj).forEach(key => { + const value = obj[key]; + const origValue = origObj[key]; + newObj[key] = this.handle(value, origValue); + }); + + return newObj; + } } /** @@ -196,7 +425,6 @@ export function handleRefFieldsWithOriginal(obj, origObj, isComponent = false) { */ export function filterSkippedMethods(methods) { if (!Array.isArray(methods)) return []; - return methods.filter(method => { const methodName = method?.name; if (!methodName) return true; diff --git a/scripts/openrpc-json-updater/utils/openrpc.utils.js b/scripts/openrpc-json-updater/utils/openrpc.utils.js index 06f100fe43..cb88cac12a 100644 --- a/scripts/openrpc-json-updater/utils/openrpc.utils.js +++ b/scripts/openrpc-json-updater/utils/openrpc.utils.js @@ -1,5 +1,5 @@ -import { compareIgnoringFormatting } from '../operations/prepare.js'; import { shouldSkipPath, shouldSkipKey } from '../config.js'; +import { compareIgnoringFormatting } from '../operations/prepare.js'; export function getMethodMap(openrpcDoc) { const map = new Map(); @@ -106,54 +106,84 @@ export function getDifferingKeysByCategory(origMethod, modMethod) { valueDiscrepancies: [], customFields: [] }; - + const differences = compareIgnoringFormatting(origMethod, modMethod) || []; - - for (const d of differences) { - if (d.path) { - const fullPath = d.path.join('.'); - - if (!fullPath || fullPath.startsWith('name')) continue; - - if (shouldSkipPath(fullPath)) continue; - - if (hasKey(origMethod, fullPath) && hasKey(modMethod, fullPath)) { - result.valueDiscrepancies.push(fullPath); - } - else if (!hasKey(origMethod, fullPath) && hasKey(modMethod, fullPath)) { - result.customFields.push(fullPath); - } - } + + // Process differences from comparison + processDifferences(differences, origMethod, modMethod, result); + + // Find missing keys in original method + findMissingKeys('', origMethod, modMethod, result); + + return result; +} + +function processDifferences(differences, origMethod, modMethod, result) { + for (const difference of differences) { + if (!difference.path) continue; + + const fullPath = difference.path.join('.'); + + if (shouldSkipDifferencePath(fullPath)) continue; + + categorizeDifference(fullPath, origMethod, modMethod, result); } - - function findMissingKeys(prefix, orig, mod) { - for (const key in orig) { - if (shouldSkipKey(key)) continue; - - const newPrefix = prefix ? `${prefix}.${key}` : key; - - if (newPrefix === 'name') continue; - - if (shouldSkipPath(newPrefix)) continue; - - if (!(key in mod)) { - result.valueDiscrepancies.push(newPrefix); - } else if ( - typeof orig[key] === 'object' && - orig[key] !== null && - typeof mod[key] === 'object' && - mod[key] !== null && - !Array.isArray(orig[key]) && - !Array.isArray(mod[key]) - ) { - findMissingKeys(newPrefix, orig[key], mod[key]); - } +} + +function shouldSkipDifferencePath(fullPath) { + return !fullPath || + fullPath.startsWith('name') || + shouldSkipPath(fullPath); +} + +function categorizeDifference(fullPath, origMethod, modMethod, result) { + const existsInOrig = hasKey(origMethod, fullPath); + const existsInMod = hasKey(modMethod, fullPath); + + if (existsInOrig && existsInMod) { + result.valueDiscrepancies.push(fullPath); + } else if (!existsInOrig && existsInMod) { + result.customFields.push(fullPath); + } +} + +function findMissingKeys(prefix, orig, mod, result) { + for (const key in orig) { + if (shouldSkipKey(key)) continue; + + const newPrefix = buildPath(prefix, key); + + if (shouldSkipMissingKeyPath(newPrefix)) continue; + + if (isKeyMissing(key, mod)) { + result.valueDiscrepancies.push(newPrefix); + } else if (shouldRecurseIntoObjects(orig[key], mod[key])) { + findMissingKeys(newPrefix, orig[key], mod[key], result); } } - - findMissingKeys('', origMethod, modMethod); - - return result; +} + +function buildPath(prefix, key) { + return prefix ? `${prefix}.${key}` : key; +} + +function shouldSkipMissingKeyPath(path) { + return path === 'name' || shouldSkipPath(path); +} + +function isKeyMissing(key, obj) { + return !(key in obj); +} + +function shouldRecurseIntoObjects(origValue, modValue) { + return isNonArrayObject(origValue) && + isNonArrayObject(modValue); +} + +function isNonArrayObject(value) { + return typeof value === 'object' && + value !== null && + !Array.isArray(value); } export function getDifferingKeys(origMethod, modMethod) { From 1f0eb3be56d1f21a5dac1afa13ade2fe88b8194d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Wed, 4 Jun 2025 09:39:42 +0200 Subject: [PATCH 04/17] chore: clean up code formatting and reorder imports in OpenRPC JSON updater scripts (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/cli.js | 2 +- scripts/openrpc-json-updater/config.js | 3 +-- scripts/openrpc-json-updater/operations/report.js | 3 +-- scripts/openrpc-json-updater/utils/file.utils.js | 3 +-- scripts/openrpc-json-updater/utils/openrpc.utils.js | 5 ++--- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/scripts/openrpc-json-updater/cli.js b/scripts/openrpc-json-updater/cli.js index 24f6e0d4ce..f679d1a42d 100644 --- a/scripts/openrpc-json-updater/cli.js +++ b/scripts/openrpc-json-updater/cli.js @@ -1,6 +1,6 @@ +import { mergeDocuments } from './operations/merge.js'; import { compareIgnoringFormatting, prepareDocuments } from './operations/prepare.js'; import { generateReport } from './operations/report.js'; -import { mergeDocuments } from './operations/merge.js'; import { readJson, writeJson } from './utils/file.utils.js'; const originalFilePath = './original-openrpc.json'; diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js index 4e74a31bf3..2dd5aac318 100644 --- a/scripts/openrpc-json-updater/config.js +++ b/scripts/openrpc-json-updater/config.js @@ -61,7 +61,7 @@ export const NOT_IMPLEMENTED_METHODS = [ export const SKIPPED_METHODS = [ ...DISCARDED_METHODS, - ...NOT_IMPLEMENTED_METHODS + ...NOT_IMPLEMENTED_METHODS, ]; export function shouldSkipMethod(methodName, path) { @@ -80,7 +80,6 @@ export function shouldSkipMethod(methodName, path) { if (methodName.startsWith(prefix)) return true; } } - return false; } diff --git a/scripts/openrpc-json-updater/operations/report.js b/scripts/openrpc-json-updater/operations/report.js index de1ef4bdc8..4921c6c332 100644 --- a/scripts/openrpc-json-updater/operations/report.js +++ b/scripts/openrpc-json-updater/operations/report.js @@ -1,5 +1,5 @@ -import { getMethodMap, getDifferingKeysByCategory, groupPaths } from '../utils/openrpc.utils.js'; import { getSkippedMethodCategory } from '../config.js'; +import { getDifferingKeysByCategory, getMethodMap, groupPaths } from '../utils/openrpc.utils.js'; export async function generateReport( originalJson, @@ -25,7 +25,6 @@ export async function generateReport( const modMethod = modifiedMethods.get(name); const { valueDiscrepancies, customFields } = getDifferingKeysByCategory(origMethod, modMethod); - if (valueDiscrepancies.length > 0 || customFields.length > 0) { changedMethods.push({ method: name, diff --git a/scripts/openrpc-json-updater/utils/file.utils.js b/scripts/openrpc-json-updater/utils/file.utils.js index f55179bb1e..6151c68184 100644 --- a/scripts/openrpc-json-updater/utils/file.utils.js +++ b/scripts/openrpc-json-updater/utils/file.utils.js @@ -5,7 +5,7 @@ export function readJson(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); return { data: JSON.parse(content), - originalContent: content + originalContent: content, }; } catch (err) { console.error(`Unable to read or parse "${filePath}":`, err); @@ -205,7 +205,6 @@ export function writeJson(filePath, data, originalContent) { const eol = originalContent.includes('\r\n') ? '\r\n' : '\n'; const formatted = formatJson(data); - const output = formatted.replace(/\n/g, eol); fs.writeFileSync(filePath, output, 'utf-8'); diff --git a/scripts/openrpc-json-updater/utils/openrpc.utils.js b/scripts/openrpc-json-updater/utils/openrpc.utils.js index cb88cac12a..eb2ff3ffc9 100644 --- a/scripts/openrpc-json-updater/utils/openrpc.utils.js +++ b/scripts/openrpc-json-updater/utils/openrpc.utils.js @@ -1,5 +1,5 @@ -import { shouldSkipPath, shouldSkipKey } from '../config.js'; import { compareIgnoringFormatting } from '../operations/prepare.js'; +import { shouldSkipKey, shouldSkipPath } from '../config.js'; export function getMethodMap(openrpcDoc) { const map = new Map(); @@ -13,7 +13,6 @@ export function getMethodMap(openrpcDoc) { function hasKey(obj, path) { if (!path) return false; - const parts = path.split('.'); let current = obj; @@ -104,7 +103,7 @@ export function groupPaths(paths, minGroupSize = 3) { export function getDifferingKeysByCategory(origMethod, modMethod) { const result = { valueDiscrepancies: [], - customFields: [] + customFields: [], }; const differences = compareIgnoringFormatting(origMethod, modMethod) || []; From b686d8318bd439edb839d4d510b8eab779b17edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Wed, 4 Jun 2025 10:45:17 +0200 Subject: [PATCH 05/17] chore: clean up code formatting and reorder imports in OpenRPC JSON updater scripts (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/config.js | 1 - .../openrpc-json-updater/operations/report.js | 2 - .../openrpc-json-updater/utils/file.utils.js | 1 - .../utils/openrpc.utils.js | 37 +++++++++---------- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js index 2dd5aac318..15bd0f53c3 100644 --- a/scripts/openrpc-json-updater/config.js +++ b/scripts/openrpc-json-updater/config.js @@ -85,7 +85,6 @@ export function shouldSkipMethod(methodName, path) { export function shouldSkipKey(key) { if (!key) return false; - for (const pattern of SKIPPED_KEYS) { if (pattern === key) return true; diff --git a/scripts/openrpc-json-updater/operations/report.js b/scripts/openrpc-json-updater/operations/report.js index 4921c6c332..7adb910f70 100644 --- a/scripts/openrpc-json-updater/operations/report.js +++ b/scripts/openrpc-json-updater/operations/report.js @@ -44,7 +44,6 @@ export async function generateReport( '\nMethods present in the original document but missing from the modified document:\n', ); console.table(missingMethods); - console.log('\nStatus explanation:'); console.log('- (discarded): Methods that have been intentionally removed'); console.log('- (not implemented): Methods that have not been implemented yet'); @@ -53,7 +52,6 @@ export async function generateReport( if (changedMethods.length > 0) { console.log('\nMethods with differences between documents:\n'); console.table(changedMethods, ['method', 'valueDiscrepancies', 'customFields']); - console.log('\nExplanation:'); console.log('- valueDiscrepancies: Fields that exist in both documents but have different values'); console.log('- customFields: Fields that exist only in the modified document (custom additions)'); diff --git a/scripts/openrpc-json-updater/utils/file.utils.js b/scripts/openrpc-json-updater/utils/file.utils.js index 6151c68184..8fa7645070 100644 --- a/scripts/openrpc-json-updater/utils/file.utils.js +++ b/scripts/openrpc-json-updater/utils/file.utils.js @@ -206,7 +206,6 @@ export function writeJson(filePath, data, originalContent) { const eol = originalContent.includes('\r\n') ? '\r\n' : '\n'; const formatted = formatJson(data); const output = formatted.replace(/\n/g, eol); - fs.writeFileSync(filePath, output, 'utf-8'); return true; } catch (err) { diff --git a/scripts/openrpc-json-updater/utils/openrpc.utils.js b/scripts/openrpc-json-updater/utils/openrpc.utils.js index eb2ff3ffc9..aaf957b399 100644 --- a/scripts/openrpc-json-updater/utils/openrpc.utils.js +++ b/scripts/openrpc-json-updater/utils/openrpc.utils.js @@ -1,5 +1,5 @@ -import { compareIgnoringFormatting } from '../operations/prepare.js'; import { shouldSkipKey, shouldSkipPath } from '../config.js'; +import { compareIgnoringFormatting } from '../operations/prepare.js'; export function getMethodMap(openrpcDoc) { const map = new Map(); @@ -15,21 +15,20 @@ function hasKey(obj, path) { if (!path) return false; const parts = path.split('.'); let current = obj; - for (let i = 0; i < parts.length; i++) { const part = parts[i]; - + if (current === undefined || current === null || typeof current !== 'object') { return false; } - + if (!(part in current)) { return false; } - + current = current[part]; } - + return true; } @@ -40,26 +39,26 @@ export function groupPaths(paths, minGroupSize = 3) { function getDepth(path) { return path.split('.').length; } - + function analyzePrefixes(paths) { const prefixCounters = {}; - + for (const path of paths) { const parts = path.split('.'); let currentPrefix = ''; - + for (let i = 0; i < parts.length - 1; i++) { currentPrefix = currentPrefix ? `${currentPrefix}.${parts[i]}` : parts[i]; prefixCounters[currentPrefix] = (prefixCounters[currentPrefix] || 0) + 1; } } - + return Object.keys(prefixCounters) .filter(prefix => prefixCounters[prefix] >= minGroupSize) .sort((a, b) => { const countDiff = prefixCounters[b] - prefixCounters[a]; if (countDiff !== 0) return countDiff; - + const depthA = getDepth(a); const depthB = getDepth(b); return depthB - depthA; @@ -69,16 +68,16 @@ export function groupPaths(paths, minGroupSize = 3) { function getSubpaths(paths, prefix) { return paths.filter(path => path.startsWith(prefix + '.') || path === prefix); } - + function groupPathsHierarchically(paths) { const remainingPaths = [...paths]; const result = []; - + const commonPrefixes = analyzePrefixes(paths); - + for (const prefix of commonPrefixes) { const matchingPaths = getSubpaths(remainingPaths, prefix); - + if (matchingPaths.length >= minGroupSize) { for (const path of matchingPaths) { const index = remainingPaths.indexOf(path); @@ -86,16 +85,16 @@ export function groupPaths(paths, minGroupSize = 3) { remainingPaths.splice(index, 1); } } - + result.push(`${prefix} (${matchingPaths.length} diffs)`); } } - + result.push(...remainingPaths); - + return result; } - + const groupedPaths = groupPathsHierarchically(paths); return groupedPaths.join(', '); } From 6d0cd1b8e11c55276228e669830d49db14ff1496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Wed, 4 Jun 2025 11:04:26 +0200 Subject: [PATCH 06/17] chore: add SPDX license identifiers and fix minor formatting issues in OpenRPC JSON updater scripts (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/cli.js | 2 ++ scripts/openrpc-json-updater/config.js | 6 ++---- scripts/openrpc-json-updater/operations/merge.js | 10 +++++----- scripts/openrpc-json-updater/operations/prepare.js | 2 ++ scripts/openrpc-json-updater/operations/report.js | 2 ++ scripts/openrpc-json-updater/utils/file.utils.js | 2 ++ scripts/openrpc-json-updater/utils/merge.utils.js | 4 +++- scripts/openrpc-json-updater/utils/openrpc.utils.js | 2 ++ 8 files changed, 20 insertions(+), 10 deletions(-) diff --git a/scripts/openrpc-json-updater/cli.js b/scripts/openrpc-json-updater/cli.js index f679d1a42d..572e9d32cb 100644 --- a/scripts/openrpc-json-updater/cli.js +++ b/scripts/openrpc-json-updater/cli.js @@ -1,3 +1,5 @@ +// 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'; diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js index 15bd0f53c3..49ac9372b6 100644 --- a/scripts/openrpc-json-updater/config.js +++ b/scripts/openrpc-json-updater/config.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + export const SKIPPED_KEYS = [ "examples", "baseFeePerBlobGas", @@ -87,24 +89,20 @@ 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; } diff --git a/scripts/openrpc-json-updater/operations/merge.js b/scripts/openrpc-json-updater/operations/merge.js index 57918d1149..6ae9a22a73 100644 --- a/scripts/openrpc-json-updater/operations/merge.js +++ b/scripts/openrpc-json-updater/operations/merge.js @@ -1,5 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 + import { shouldSkipKey, shouldSkipMethod, shouldSkipPath } from '../config.js'; -import { getMethodMap, getDifferingKeys } from '../utils/openrpc.utils.js'; import { filterSkippedMethods, findRefPaths, @@ -12,6 +13,7 @@ import { setNestedValue, setObjectByPath, } from '../utils/merge.utils.js'; +import { getDifferingKeys, getMethodMap } from '../utils/openrpc.utils.js'; class MergeDocuments { /** @@ -66,12 +68,10 @@ class MergeDocuments { filteredModified.methods.push(origMethod); continue; } - const modMethod = modifiedMap.get(name); - + // Process $ref fields this.processRefFields(origMethod, modMethod); - // Process differing keys this.processDifferingKeys(origMethod, modMethod); } @@ -444,7 +444,7 @@ class MergeDocuments { document.components = handleRefFieldsWithOriginal( document.components, originalDocument.components, - true + true, ); } } diff --git a/scripts/openrpc-json-updater/operations/prepare.js b/scripts/openrpc-json-updater/operations/prepare.js index 853eca721b..82c51694a8 100644 --- a/scripts/openrpc-json-updater/operations/prepare.js +++ b/scripts/openrpc-json-updater/operations/prepare.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + import diff from 'deep-diff'; export function prepareDocuments(originalJson, modifiedJson) { diff --git a/scripts/openrpc-json-updater/operations/report.js b/scripts/openrpc-json-updater/operations/report.js index 7adb910f70..4dfa6492ca 100644 --- a/scripts/openrpc-json-updater/operations/report.js +++ b/scripts/openrpc-json-updater/operations/report.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + import { getSkippedMethodCategory } from '../config.js'; import { getDifferingKeysByCategory, getMethodMap, groupPaths } from '../utils/openrpc.utils.js'; diff --git a/scripts/openrpc-json-updater/utils/file.utils.js b/scripts/openrpc-json-updater/utils/file.utils.js index 8fa7645070..9faee089f6 100644 --- a/scripts/openrpc-json-updater/utils/file.utils.js +++ b/scripts/openrpc-json-updater/utils/file.utils.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + import fs from 'fs'; export function readJson(filePath) { diff --git a/scripts/openrpc-json-updater/utils/merge.utils.js b/scripts/openrpc-json-updater/utils/merge.utils.js index 78104b6344..f0528316ba 100644 --- a/scripts/openrpc-json-updater/utils/merge.utils.js +++ b/scripts/openrpc-json-updater/utils/merge.utils.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + import { shouldSkipKey, shouldSkipMethod } from '../config.js'; /** @@ -221,7 +223,7 @@ class RefPathFinder { addRefPath(path, ref, paths) { paths.push({ path, - ref + ref, }); } diff --git a/scripts/openrpc-json-updater/utils/openrpc.utils.js b/scripts/openrpc-json-updater/utils/openrpc.utils.js index aaf957b399..52b2faf92f 100644 --- a/scripts/openrpc-json-updater/utils/openrpc.utils.js +++ b/scripts/openrpc-json-updater/utils/openrpc.utils.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + import { shouldSkipKey, shouldSkipPath } from '../config.js'; import { compareIgnoringFormatting } from '../operations/prepare.js'; From c102620d26cdf6fe41ffa4df5693e79c3a9fe977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Wed, 4 Jun 2025 11:14:55 +0200 Subject: [PATCH 07/17] chore: pin peter-evans/create-pull-request action to v7.0.8 in OpenRPC JSON updater workflow (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- .github/workflows/openrpc-updater.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/openrpc-updater.yml b/.github/workflows/openrpc-updater.yml index 24f45c7c9f..e0a66271da 100644 --- a/.github/workflows/openrpc-updater.yml +++ b/.github/workflows/openrpc-updater.yml @@ -114,7 +114,7 @@ jobs: - name: Create Pull Request if: env.SKIP_PR != 'true' - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: token: ${{ secrets.PAT_TOKEN }} commit-message: Update OpenRPC JSON From d9f620c370ccd8292b96564b8d98d865af0cee87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Wed, 4 Jun 2025 12:00:03 +0200 Subject: [PATCH 08/17] chore: remove npm cache configuration from OpenRPC JSON updater workflow (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- .github/workflows/openrpc-updater.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/openrpc-updater.yml b/.github/workflows/openrpc-updater.yml index e0a66271da..4fb338c54f 100644 --- a/.github/workflows/openrpc-updater.yml +++ b/.github/workflows/openrpc-updater.yml @@ -62,8 +62,6 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '22' - cache: 'npm' - cache-dependency-path: 'scripts/openrpc-json-updater/package-lock.json' - name: Install dependencies run: | From b64ee284d8172684bbcd0db9c7903d8ab35793dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Fri, 6 Jun 2025 11:22:31 +0200 Subject: [PATCH 09/17] chore: enhance OpenRPC JSON updater with configuration updates, improved README, and workflow refinements (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- .github/workflows/openrpc-updater.yml | 13 ++++--- scripts/openrpc-json-updater/README.md | 39 +++++++++++++++++++ .../openrpc-json-updater/operations/merge.js | 9 +---- .../original-openrpc.json | 1 - scripts/openrpc-json-updater/package.json | 6 ++- 5 files changed, 54 insertions(+), 14 deletions(-) delete mode 100644 scripts/openrpc-json-updater/original-openrpc.json diff --git a/.github/workflows/openrpc-updater.yml b/.github/workflows/openrpc-updater.yml index 4fb338c54f..cae004b63d 100644 --- a/.github/workflows/openrpc-updater.yml +++ b/.github/workflows/openrpc-updater.yml @@ -45,7 +45,7 @@ jobs: uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: 'main' - token: ${{ secrets.PAT_TOKEN }} + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - name: Download openrpc.json artifact uses: actions/download-artifact@v4 @@ -77,6 +77,10 @@ jobs: 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: | @@ -114,7 +118,7 @@ jobs: if: env.SKIP_PR != 'true' uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: - token: ${{ secrets.PAT_TOKEN }} + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} commit-message: Update OpenRPC JSON title: 'Update OpenRPC JSON' body: | @@ -128,6 +132,5 @@ jobs: ``` branch: ${{ env.UNIQUE_BRANCH }} base: 'main' - add-paths: | - docs/openrpc.json - delete-branch: false \ No newline at end of file + add-paths: docs/openrpc.json + delete-branch: true \ No newline at end of file diff --git a/scripts/openrpc-json-updater/README.md b/scripts/openrpc-json-updater/README.md index 3af31b4114..38db445282 100644 --- a/scripts/openrpc-json-updater/README.md +++ b/scripts/openrpc-json-updater/README.md @@ -9,6 +9,45 @@ This tool is used with the `openrpc-updater.yml` workflow that automatically: - 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 diff --git a/scripts/openrpc-json-updater/operations/merge.js b/scripts/openrpc-json-updater/operations/merge.js index 6ae9a22a73..c412b11df5 100644 --- a/scripts/openrpc-json-updater/operations/merge.js +++ b/scripts/openrpc-json-updater/operations/merge.js @@ -30,10 +30,10 @@ class MergeDocuments { const filteredOriginal = this.filterDocument(originalJson); const filteredModified = this.filterDocument(modifiedJson); - // Step 2: Merge methods from original to modified + // Step 2: Merge methods from original to hedera's modified file this.mergeMethods(filteredOriginal, filteredModified); - // Step 3: Merge components from original to modified + // Step 3: Merge components from original to hedera's modified file this.mergeComponents(filteredOriginal, filteredModified); // Step 4: Process the final document @@ -63,16 +63,13 @@ class MergeDocuments { const name = origMethod.name; if (!name) continue; - // If method doesn't exist in modified, add it if (!modifiedMap.has(name)) { filteredModified.methods.push(origMethod); continue; } const modMethod = modifiedMap.get(name); - // Process $ref fields this.processRefFields(origMethod, modMethod); - // Process differing keys this.processDifferingKeys(origMethod, modMethod); } } @@ -184,10 +181,8 @@ class MergeDocuments { continue; } - // Skip if path contains a $ref if (this.shouldSkipDueToRef(modMethod, path)) continue; - // Set the value from original to modified if (path.includes('.')) { setNestedValue(modMethod, path, valueFromOriginal); } else { diff --git a/scripts/openrpc-json-updater/original-openrpc.json b/scripts/openrpc-json-updater/original-openrpc.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/scripts/openrpc-json-updater/original-openrpc.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/scripts/openrpc-json-updater/package.json b/scripts/openrpc-json-updater/package.json index 7a3187a40d..4fad7b75ed 100644 --- a/scripts/openrpc-json-updater/package.json +++ b/scripts/openrpc-json-updater/package.json @@ -1,6 +1,10 @@ { + "name": "openrpc-updater", + "description": "OpenRPC JSON Updater Tool", + "author": "Hedera Team", + "license": "Apache-2.0", "type": "module", - "dependencies": { + "devDependencies": { "deep-diff": "^1.0.2" } } From eabc938449d947b88c53ee736c5e45dd5eceeaa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Fri, 6 Jun 2025 11:38:12 +0200 Subject: [PATCH 10/17] chore: update OpenRPC JSON updater README with file creation details (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/openrpc-json-updater/README.md b/scripts/openrpc-json-updater/README.md index 38db445282..5c4578cdfb 100644 --- a/scripts/openrpc-json-updater/README.md +++ b/scripts/openrpc-json-updater/README.md @@ -9,7 +9,6 @@ This tool is used with the `openrpc-updater.yml` workflow that automatically: - 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 @@ -76,3 +75,7 @@ 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. From 91eb85b7f76e2574dc4ae18cd6452ef7644047d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Fri, 6 Jun 2025 12:09:02 +0200 Subject: [PATCH 11/17] chore: clean up formatting (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/README.md | 20 +++-- scripts/openrpc-json-updater/config.js | 89 +++++++++---------- .../openrpc-json-updater/operations/merge.js | 33 +++---- .../openrpc-json-updater/operations/report.js | 9 +- .../openrpc-json-updater/utils/file.utils.js | 27 +++--- .../openrpc-json-updater/utils/merge.utils.js | 14 +-- .../utils/openrpc.utils.js | 15 ++-- 7 files changed, 92 insertions(+), 115 deletions(-) diff --git a/scripts/openrpc-json-updater/README.md b/scripts/openrpc-json-updater/README.md index 5c4578cdfb..43cf8003b1 100644 --- a/scripts/openrpc-json-updater/README.md +++ b/scripts/openrpc-json-updater/README.md @@ -5,12 +5,14 @@ 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` @@ -18,16 +20,19 @@ Add the token as a repository secret: ## Exclusions and Customizations (config.js) -This tool applies several filters when processing OpenRPC specifications to align them with our implementation requirements: +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_getRawBlock` - `debug_getRawHeader` - `debug_getRawReceipts` - `debug_getRawTransaction` @@ -37,15 +42,19 @@ These debug methods are excluded because they are not implemented in our current ### 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. +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. +These customizations ensure the OpenRPC specification aligns with our implementation's specific requirements and +documentation standards. ## Install @@ -78,4 +87,5 @@ 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. +The script creates a file in the `docs/openrpc.json` path, so the application must be run with appropriate file system +permissions. diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js index 49ac9372b6..db22ccebe0 100644 --- a/scripts/openrpc-json-updater/config.js +++ b/scripts/openrpc-json-updater/config.js @@ -1,70 +1,61 @@ // SPDX-License-Identifier: Apache-2.0 -export const SKIPPED_KEYS = [ - "examples", - "baseFeePerBlobGas", - "blobGasUsedRatio", -]; +export const SKIPPED_KEYS = ['examples', 'baseFeePerBlobGas', 'blobGasUsedRatio']; export const CUSTOM_FIELDS = [ - "eth_accounts.description", - "eth_accounts.result.description", - "eth_accounts.result.schema.title", + 'eth_accounts.description', + 'eth_accounts.result.description', + 'eth_accounts.result.schema.title', - "eth_call.summary", + 'eth_call.summary', - "eth_coinbase.summary", - "eth_blobBaseFee.summary", + 'eth_coinbase.summary', + 'eth_blobBaseFee.summary', - "eth_feeHistory.summary", - "eth_feeHistory.description", - "eth_feeHistory.params.2.description", - "eth_feeHistory.result.name", - "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_feeHistory.summary', + 'eth_feeHistory.description', + 'eth_feeHistory.params.2.description', + 'eth_feeHistory.result.name', + '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_gasPrice.summary", + 'eth_gasPrice.summary', - "eth_getBalance.result.schema.title", + 'eth_getBalance.result.schema.title', - "eth_getBlockTransactionCountByHash.result.name", - "eth_getBlockTransactionCountByNumber.result.name", + 'eth_getBlockTransactionCountByHash.result.name', + 'eth_getBlockTransactionCountByNumber.result.name', - "eth_getLogs.summary", - "eth_getStorageAt.summary", - "eth_getStorageAt.params.1.name", - "eth_getStorageAt.result.name", + 'eth_getLogs.summary', + 'eth_getStorageAt.summary', + 'eth_getStorageAt.params.1.name', + 'eth_getStorageAt.result.name', - "eth_getTransactionCount.summary", - "eth_getTransactionCount.result.name", - "eth_getTransactionCount.result.schema.title", + 'eth_getTransactionCount.summary', + 'eth_getTransactionCount.result.name', + 'eth_getTransactionCount.result.schema.title', - "eth_maxPriorityFeePerGas.summary", - "eth_maxPriorityFeePerGas.result.schema.description", - "eth_maxPriorityFeePerGas.result.schema.title", + 'eth_maxPriorityFeePerGas.summary', + 'eth_maxPriorityFeePerGas.result.schema.description', + 'eth_maxPriorityFeePerGas.result.schema.title', - "eth_newBlockFilter.summary", - "eth_newBlockFilter.result.name", + 'eth_newBlockFilter.summary', + 'eth_newBlockFilter.result.name', ]; -export const DISCARDED_METHODS = [ - "engine_*", -]; +export const DISCARDED_METHODS = ['engine_*']; export const NOT_IMPLEMENTED_METHODS = [ - "debug_getBadBlocks", - "debug_getRawBlock", - "debug_getRawHeader", - "debug_getRawReceipts", - "debug_getRawTransaction", + 'debug_getBadBlocks', + 'debug_getRawBlock', + 'debug_getRawHeader', + 'debug_getRawReceipts', + 'debug_getRawTransaction', ]; -export const SKIPPED_METHODS = [ - ...DISCARDED_METHODS, - ...NOT_IMPLEMENTED_METHODS, -]; +export const SKIPPED_METHODS = [...DISCARDED_METHODS, ...NOT_IMPLEMENTED_METHODS]; export function shouldSkipMethod(methodName, path) { if (!methodName) return false; @@ -120,11 +111,11 @@ export function getSkippedMethodCategory(methodName) { return false; }; - if (DISCARDED_METHODS.some(pattern => matchesPattern(pattern, methodName))) { + if (DISCARDED_METHODS.some((pattern) => matchesPattern(pattern, methodName))) { return 'discarded'; } - if (NOT_IMPLEMENTED_METHODS.some(pattern => matchesPattern(pattern, methodName))) { + if (NOT_IMPLEMENTED_METHODS.some((pattern) => matchesPattern(pattern, methodName))) { return 'not implemented'; } diff --git a/scripts/openrpc-json-updater/operations/merge.js b/scripts/openrpc-json-updater/operations/merge.js index c412b11df5..6cf5ebf96a 100644 --- a/scripts/openrpc-json-updater/operations/merge.js +++ b/scripts/openrpc-json-updater/operations/merge.js @@ -128,10 +128,7 @@ class MergeDocuments { * @private */ areValidObjects(targetObj, origObj) { - return targetObj && - typeof targetObj === 'object' && - origObj && - typeof origObj === 'object'; + return targetObj && typeof targetObj === 'object' && origObj && typeof origObj === 'object'; } /** @@ -151,7 +148,7 @@ class MergeDocuments { * @private */ replaceRootObject(targetObj, sourceObj) { - Object.keys(targetObj).forEach(key => { + Object.keys(targetObj).forEach((key) => { delete targetObj[key]; }); @@ -225,7 +222,7 @@ class MergeDocuments { const originalComponents = filteredOriginal.components; const modifiedComponents = filteredModified.components; - Object.keys(originalComponents).forEach(sectionName => { + Object.keys(originalComponents).forEach((sectionName) => { this.mergeSectionComponents(originalComponents[sectionName], modifiedComponents, sectionName); }); } @@ -267,7 +264,7 @@ class MergeDocuments { const validKeys = this.getValidKeysFromSection(originalSection); - validKeys.forEach(key => { + validKeys.forEach((key) => { this.mergeComponentKey(originalSection[key], modifiedComponents[sectionName], key); }); } @@ -291,7 +288,7 @@ class MergeDocuments { * @private */ getValidKeysFromSection(section) { - return Object.keys(section).filter(key => !shouldSkipKey(key)); + return Object.keys(section).filter((key) => !shouldSkipKey(key)); } /** @@ -342,9 +339,7 @@ class MergeDocuments { * @private */ isValidDocument(document) { - return document && - document.methods && - Array.isArray(document.methods); + return document && document.methods && Array.isArray(document.methods); } /** @@ -374,7 +369,7 @@ class MergeDocuments { * @private */ filterValidMethods(methods) { - return methods.filter(method => { + return methods.filter((method) => { const methodName = method?.name; if (!methodName) return true; return !shouldSkipMethod(methodName); @@ -388,7 +383,7 @@ class MergeDocuments { * @private */ removeSkippedKeysFromMethods(methods) { - return methods.map(method => removeSkippedKeys(method)); + return methods.map((method) => removeSkippedKeys(method)); } /** @@ -417,11 +412,9 @@ class MergeDocuments { const origMethodMap = getMethodMap(originalDocument); - document.methods = document.methods.map(method => { + document.methods = document.methods.map((method) => { const origMethod = origMethodMap.get(method.name); - return origMethod - ? handleRefFieldsWithOriginal(method, origMethod) - : method; + return origMethod ? handleRefFieldsWithOriginal(method, origMethod) : method; }); } @@ -436,11 +429,7 @@ class MergeDocuments { return; } - document.components = handleRefFieldsWithOriginal( - document.components, - originalDocument.components, - true, - ); + document.components = handleRefFieldsWithOriginal(document.components, originalDocument.components, true); } } diff --git a/scripts/openrpc-json-updater/operations/report.js b/scripts/openrpc-json-updater/operations/report.js index 4dfa6492ca..6a20cf262b 100644 --- a/scripts/openrpc-json-updater/operations/report.js +++ b/scripts/openrpc-json-updater/operations/report.js @@ -3,10 +3,7 @@ import { getSkippedMethodCategory } from '../config.js'; import { getDifferingKeysByCategory, getMethodMap, groupPaths } from '../utils/openrpc.utils.js'; -export async function generateReport( - originalJson, - modifiedJson, -) { +export async function generateReport(originalJson, modifiedJson) { const originalMethods = getMethodMap(originalJson); const modifiedMethods = getMethodMap(modifiedJson); @@ -42,9 +39,7 @@ export async function generateReport( } if (missingMethods.length > 0) { - console.log( - '\nMethods present in the original document but missing from the modified document:\n', - ); + console.log('\nMethods present in the original document but missing from the modified document:\n'); console.table(missingMethods); console.log('\nStatus explanation:'); console.log('- (discarded): Methods that have been intentionally removed'); diff --git a/scripts/openrpc-json-updater/utils/file.utils.js b/scripts/openrpc-json-updater/utils/file.utils.js index 9faee089f6..159a30782d 100644 --- a/scripts/openrpc-json-updater/utils/file.utils.js +++ b/scripts/openrpc-json-updater/utils/file.utils.js @@ -61,11 +61,13 @@ class JsonFormatter { * @returns {boolean} True if the value is primitive */ isPrimitive(value) { - return value === null || + return ( + value === null || value === undefined || typeof value === 'number' || typeof value === 'boolean' || - typeof value === 'string'; + typeof value === 'string' + ); } /** @@ -115,8 +117,7 @@ class JsonFormatter { const maxCompactLength = 8; const maxStringLength = 40; - return array.length <= maxCompactLength && - array.every(item => this.isCompactableItem(item, maxStringLength)); + return array.length <= maxCompactLength && array.every((item) => this.isCompactableItem(item, maxStringLength)); } /** @@ -126,9 +127,11 @@ class JsonFormatter { * @returns {boolean} True if item can be formatted compactly */ isCompactableItem(item, maxStringLength) { - return (typeof item === 'string' && item.length < maxStringLength) || + return ( + (typeof item === 'string' && item.length < maxStringLength) || typeof item === 'number' || - typeof item === 'boolean'; + typeof item === 'boolean' + ); } /** @@ -137,9 +140,7 @@ class JsonFormatter { * @returns {string} Compact formatted array */ formatCompactArray(array) { - const items = array - .map(item => this.createChildFormatter().format(item)) - .join(', '); + const items = array.map((item) => this.createChildFormatter().format(item)).join(', '); return `[${items}]`; } @@ -150,9 +151,7 @@ class JsonFormatter { */ formatExpandedArray(array) { const childFormatter = this.createChildFormatter(); - const items = array - .map(item => this.nextIndent + childFormatter.format(item)) - .join(',\n'); + const items = array.map((item) => this.nextIndent + childFormatter.format(item)).join(',\n'); return `[\n${items}\n${this.currentIndent}]`; } @@ -170,9 +169,7 @@ class JsonFormatter { } const childFormatter = this.createChildFormatter(); - const props = entries - .map(([key, value]) => this.formatObjectProperty(key, value, childFormatter)) - .join(',\n'); + const props = entries.map(([key, value]) => this.formatObjectProperty(key, value, childFormatter)).join(',\n'); return `{\n${props}\n${this.currentIndent}}`; } diff --git a/scripts/openrpc-json-updater/utils/merge.utils.js b/scripts/openrpc-json-updater/utils/merge.utils.js index f0528316ba..b2a4f53d79 100644 --- a/scripts/openrpc-json-updater/utils/merge.utils.js +++ b/scripts/openrpc-json-updater/utils/merge.utils.js @@ -59,7 +59,7 @@ export function removeSkippedKeys(obj) { if (!obj || typeof obj !== 'object') return obj; if (Array.isArray(obj)) { - return obj.map(item => removeSkippedKeys(item)); + return obj.map((item) => removeSkippedKeys(item)); } const result = {}; @@ -79,11 +79,11 @@ export function handleRefField(obj) { if (!obj || typeof obj !== 'object') return obj; if (Array.isArray(obj)) { - return obj.map(item => handleRefField(item)); + return obj.map((item) => handleRefField(item)); } if (obj['$ref'] !== undefined) { - return { '$ref': obj['$ref'] }; + return { $ref: obj['$ref'] }; } const result = {}; @@ -234,7 +234,7 @@ class RefPathFinder { * @param {Array} paths - The accumulated paths array */ processObjectProperties(obj, currentPath, paths) { - Object.keys(obj).forEach(key => { + Object.keys(obj).forEach((key) => { const value = obj[key]; if (this.shouldProcessProperty(value)) { const propertyPath = this.buildPath(currentPath, key); @@ -400,7 +400,7 @@ class RefFieldHandler { * @returns {Object} Object with only $ref field */ createRefOnlyObject(refValue) { - return { '$ref': refValue }; + return { $ref: refValue }; } /** @@ -412,7 +412,7 @@ class RefFieldHandler { processObjectProperties(obj, origObj) { const newObj = {}; - Object.keys(obj).forEach(key => { + Object.keys(obj).forEach((key) => { const value = obj[key]; const origValue = origObj[key]; newObj[key] = this.handle(value, origValue); @@ -427,7 +427,7 @@ class RefFieldHandler { */ export function filterSkippedMethods(methods) { if (!Array.isArray(methods)) return []; - return methods.filter(method => { + return methods.filter((method) => { const methodName = method?.name; if (!methodName) return true; return !shouldSkipMethod(methodName); diff --git a/scripts/openrpc-json-updater/utils/openrpc.utils.js b/scripts/openrpc-json-updater/utils/openrpc.utils.js index 52b2faf92f..301eec1f26 100644 --- a/scripts/openrpc-json-updater/utils/openrpc.utils.js +++ b/scripts/openrpc-json-updater/utils/openrpc.utils.js @@ -56,7 +56,7 @@ export function groupPaths(paths, minGroupSize = 3) { } return Object.keys(prefixCounters) - .filter(prefix => prefixCounters[prefix] >= minGroupSize) + .filter((prefix) => prefixCounters[prefix] >= minGroupSize) .sort((a, b) => { const countDiff = prefixCounters[b] - prefixCounters[a]; if (countDiff !== 0) return countDiff; @@ -68,7 +68,7 @@ export function groupPaths(paths, minGroupSize = 3) { } function getSubpaths(paths, prefix) { - return paths.filter(path => path.startsWith(prefix + '.') || path === prefix); + return paths.filter((path) => path.startsWith(prefix + '.') || path === prefix); } function groupPathsHierarchically(paths) { @@ -131,9 +131,7 @@ function processDifferences(differences, origMethod, modMethod, result) { } function shouldSkipDifferencePath(fullPath) { - return !fullPath || - fullPath.startsWith('name') || - shouldSkipPath(fullPath); + return !fullPath || fullPath.startsWith('name') || shouldSkipPath(fullPath); } function categorizeDifference(fullPath, origMethod, modMethod, result) { @@ -176,14 +174,11 @@ function isKeyMissing(key, obj) { } function shouldRecurseIntoObjects(origValue, modValue) { - return isNonArrayObject(origValue) && - isNonArrayObject(modValue); + return isNonArrayObject(origValue) && isNonArrayObject(modValue); } function isNonArrayObject(value) { - return typeof value === 'object' && - value !== null && - !Array.isArray(value); + return typeof value === 'object' && value !== null && !Array.isArray(value); } export function getDifferingKeys(origMethod, modMethod) { From c428955e331932b624aa05d79052d0473fcd3112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Mon, 9 Jun 2025 11:07:32 +0200 Subject: [PATCH 12/17] chore: restrict OpenRPC updater workflow triggers to `docs/openrpc.json` changes (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- .github/workflows/openrpc-updater.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/openrpc-updater.yml b/.github/workflows/openrpc-updater.yml index cae004b63d..9872df419b 100644 --- a/.github/workflows/openrpc-updater.yml +++ b/.github/workflows/openrpc-updater.yml @@ -5,6 +5,8 @@ on: push: branches: - main + paths: + - 'docs/openrpc.json' jobs: clone-and-build-execution-apis: From 56f3a8c76bc078a2f21756a2c7837f385096239c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Fri, 13 Jun 2025 09:34:11 +0200 Subject: [PATCH 13/17] chore: remove `customFields` handling from OpenRPC JSON updater scripts (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/config.js | 11 ++--------- scripts/openrpc-json-updater/operations/merge.js | 7 ------- scripts/openrpc-json-updater/operations/report.js | 8 +++----- scripts/openrpc-json-updater/utils/openrpc.utils.js | 7 ++----- 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js index db22ccebe0..0c59d94874 100644 --- a/scripts/openrpc-json-updater/config.js +++ b/scripts/openrpc-json-updater/config.js @@ -3,15 +3,6 @@ export const SKIPPED_KEYS = ['examples', 'baseFeePerBlobGas', 'blobGasUsedRatio']; export const CUSTOM_FIELDS = [ - 'eth_accounts.description', - 'eth_accounts.result.description', - 'eth_accounts.result.schema.title', - - 'eth_call.summary', - - 'eth_coinbase.summary', - 'eth_blobBaseFee.summary', - 'eth_feeHistory.summary', 'eth_feeHistory.description', 'eth_feeHistory.params.2.description', @@ -53,6 +44,8 @@ export const NOT_IMPLEMENTED_METHODS = [ 'debug_getRawHeader', 'debug_getRawReceipts', 'debug_getRawTransaction', + 'eth_coinbase', + 'eth_blobBaseFee', ]; export const SKIPPED_METHODS = [...DISCARDED_METHODS, ...NOT_IMPLEMENTED_METHODS]; diff --git a/scripts/openrpc-json-updater/operations/merge.js b/scripts/openrpc-json-updater/operations/merge.js index 6cf5ebf96a..62d1f7642d 100644 --- a/scripts/openrpc-json-updater/operations/merge.js +++ b/scripts/openrpc-json-updater/operations/merge.js @@ -171,13 +171,6 @@ class MergeDocuments { const valueFromOriginal = getNestedValue(origMethod, path); - const existsInOriginal = hasNestedPath(origMethod, path); - const existsInModified = hasNestedPath(modMethod, path); - - if (!existsInOriginal && existsInModified) { - continue; - } - if (this.shouldSkipDueToRef(modMethod, path)) continue; if (path.includes('.')) { diff --git a/scripts/openrpc-json-updater/operations/report.js b/scripts/openrpc-json-updater/operations/report.js index 6a20cf262b..56c6b70614 100644 --- a/scripts/openrpc-json-updater/operations/report.js +++ b/scripts/openrpc-json-updater/operations/report.js @@ -23,12 +23,11 @@ export async function generateReport(originalJson, modifiedJson) { if (!modifiedMethods.has(name)) continue; const modMethod = modifiedMethods.get(name); - const { valueDiscrepancies, customFields } = getDifferingKeysByCategory(origMethod, modMethod); - if (valueDiscrepancies.length > 0 || customFields.length > 0) { + const { valueDiscrepancies } = getDifferingKeysByCategory(origMethod, modMethod); + if (valueDiscrepancies.length > 0) { changedMethods.push({ method: name, valueDiscrepancies: groupPaths(valueDiscrepancies, 3), - customFields: groupPaths(customFields, 3), }); } } @@ -48,10 +47,9 @@ export async function generateReport(originalJson, modifiedJson) { if (changedMethods.length > 0) { console.log('\nMethods with differences between documents:\n'); - console.table(changedMethods, ['method', 'valueDiscrepancies', 'customFields']); + console.table(changedMethods, ['method', 'valueDiscrepancies']); console.log('\nExplanation:'); console.log('- valueDiscrepancies: Fields that exist in both documents but have different values'); - console.log('- customFields: Fields that exist only in the modified document (custom additions)'); console.log('- Entries with format "path (N diffs)" indicate N differences within that path'); } } diff --git a/scripts/openrpc-json-updater/utils/openrpc.utils.js b/scripts/openrpc-json-updater/utils/openrpc.utils.js index 301eec1f26..8cdc08c974 100644 --- a/scripts/openrpc-json-updater/utils/openrpc.utils.js +++ b/scripts/openrpc-json-updater/utils/openrpc.utils.js @@ -104,7 +104,6 @@ export function groupPaths(paths, minGroupSize = 3) { export function getDifferingKeysByCategory(origMethod, modMethod) { const result = { valueDiscrepancies: [], - customFields: [], }; const differences = compareIgnoringFormatting(origMethod, modMethod) || []; @@ -140,8 +139,6 @@ function categorizeDifference(fullPath, origMethod, modMethod, result) { if (existsInOrig && existsInMod) { result.valueDiscrepancies.push(fullPath); - } else if (!existsInOrig && existsInMod) { - result.customFields.push(fullPath); } } @@ -182,6 +179,6 @@ function isNonArrayObject(value) { } export function getDifferingKeys(origMethod, modMethod) { - const { valueDiscrepancies, customFields } = getDifferingKeysByCategory(origMethod, modMethod); - return [...new Set([...valueDiscrepancies, ...customFields])]; + const { valueDiscrepancies } = getDifferingKeysByCategory(origMethod, modMethod); + return [...new Set(valueDiscrepancies)]; } From 567c7dd8bb4436316e90de9c95b86e331bd692d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Fri, 13 Jun 2025 14:18:58 +0200 Subject: [PATCH 14/17] chore: update OpenRPC JSON updater config by pruning unused fields and adding new methods (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/config.js | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js index 0c59d94874..84d865e729 100644 --- a/scripts/openrpc-json-updater/config.js +++ b/scripts/openrpc-json-updater/config.js @@ -6,34 +6,12 @@ export const CUSTOM_FIELDS = [ 'eth_feeHistory.summary', 'eth_feeHistory.description', 'eth_feeHistory.params.2.description', - 'eth_feeHistory.result.name', '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_gasPrice.summary', - - 'eth_getBalance.result.schema.title', - - 'eth_getBlockTransactionCountByHash.result.name', - 'eth_getBlockTransactionCountByNumber.result.name', - - 'eth_getLogs.summary', - 'eth_getStorageAt.summary', - 'eth_getStorageAt.params.1.name', - 'eth_getStorageAt.result.name', - 'eth_getTransactionCount.summary', - 'eth_getTransactionCount.result.name', - 'eth_getTransactionCount.result.schema.title', - - 'eth_maxPriorityFeePerGas.summary', 'eth_maxPriorityFeePerGas.result.schema.description', - 'eth_maxPriorityFeePerGas.result.schema.title', - - 'eth_newBlockFilter.summary', - 'eth_newBlockFilter.result.name', ]; export const DISCARDED_METHODS = ['engine_*']; @@ -46,6 +24,8 @@ export const NOT_IMPLEMENTED_METHODS = [ 'debug_getRawTransaction', 'eth_coinbase', 'eth_blobBaseFee', + 'eth_syncing', + 'eth_getProof', ]; export const SKIPPED_METHODS = [...DISCARDED_METHODS, ...NOT_IMPLEMENTED_METHODS]; From 1de58c1b5de26d0645451d762dfe592a6d2fb410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Fri, 13 Jun 2025 15:22:16 +0200 Subject: [PATCH 15/17] chore: enhance OpenRPC JSON updater with refined method handling and removal of deprecated logic (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- .../openrpc-json-updater/operations/merge.js | 21 +++------------ .../openrpc-json-updater/operations/report.js | 26 ++++++++++++++----- .../openrpc-json-updater/utils/merge.utils.js | 14 +++------- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/scripts/openrpc-json-updater/operations/merge.js b/scripts/openrpc-json-updater/operations/merge.js index 62d1f7642d..1609405ddf 100644 --- a/scripts/openrpc-json-updater/operations/merge.js +++ b/scripts/openrpc-json-updater/operations/merge.js @@ -8,7 +8,6 @@ import { getObjectByPath, handleRefField, handleRefFieldsWithOriginal, - hasNestedPath, removeSkippedKeys, setNestedValue, setObjectByPath, @@ -231,7 +230,7 @@ class MergeDocuments { } /** - * Ensures the components object exists in the target document + * Ensures the component object exists in the target document * @param {Object} document - The document to ensure components exist in * @private */ @@ -328,7 +327,7 @@ class MergeDocuments { /** * Validates if the document has the required structure * @param {Object} document - The document to validate - * @returns {boolean} True if document is valid + * @returns {boolean} True if a document is valid * @private */ isValidDocument(document) { @@ -351,24 +350,10 @@ class MergeDocuments { * @private */ filterAndCleanMethods(document) { - document.methods = this.filterValidMethods(document.methods); + document.methods = filterSkippedMethods(document.methods); document.methods = this.removeSkippedKeysFromMethods(document.methods); } - /** - * Filters methods that should not be skipped - * @param {Array} methods - Array of methods to filter - * @returns {Array} Filtered array of methods - * @private - */ - filterValidMethods(methods) { - return methods.filter((method) => { - const methodName = method?.name; - if (!methodName) return true; - return !shouldSkipMethod(methodName); - }); - } - /** * Removes skipped keys from all methods * @param {Array} methods - Array of methods to clean diff --git a/scripts/openrpc-json-updater/operations/report.js b/scripts/openrpc-json-updater/operations/report.js index 56c6b70614..e5d190fc50 100644 --- a/scripts/openrpc-json-updater/operations/report.js +++ b/scripts/openrpc-json-updater/operations/report.js @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -import { getSkippedMethodCategory } from '../config.js'; +import { getSkippedMethodCategory, NOT_IMPLEMENTED_METHODS } from '../config.js'; import { getDifferingKeysByCategory, getMethodMap, groupPaths } from '../utils/openrpc.utils.js'; export async function generateReport(originalJson, modifiedJson) { @@ -8,19 +8,33 @@ export async function generateReport(originalJson, modifiedJson) { const modifiedMethods = getMethodMap(modifiedJson); const missingMethods = []; + + for (const method of NOT_IMPLEMENTED_METHODS) { + missingMethods.push({ + missingMethod: method, + status: 'not implemented', + }); + } for (const name of originalMethods.keys()) { if (!modifiedMethods.has(name)) { - const category = getSkippedMethodCategory(name); - missingMethods.push({ - missingMethod: name, - status: category ? `${category}` : 'a new method', - }); + const alreadyReported = missingMethods.some(item => item.missingMethod === name); + if (!alreadyReported) { + const category = getSkippedMethodCategory(name); + missingMethods.push({ + missingMethod: name, + status: category ? `${category}` : 'a new method', + }); + } } } const changedMethods = []; for (const [name, origMethod] of originalMethods) { if (!modifiedMethods.has(name)) continue; + const category = getSkippedMethodCategory(name); + if (category === 'discarded' || category === 'not implemented') { + continue; + } const modMethod = modifiedMethods.get(name); const { valueDiscrepancies } = getDifferingKeysByCategory(origMethod, modMethod); diff --git a/scripts/openrpc-json-updater/utils/merge.utils.js b/scripts/openrpc-json-updater/utils/merge.utils.js index b2a4f53d79..c3932074d9 100644 --- a/scripts/openrpc-json-updater/utils/merge.utils.js +++ b/scripts/openrpc-json-updater/utils/merge.utils.js @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -import { shouldSkipKey, shouldSkipMethod } from '../config.js'; +import { getSkippedMethodCategory,shouldSkipKey } from '../config.js'; /** * Sets a nested value in an object using a dot-notation path @@ -44,14 +44,6 @@ export function getNestedValue(obj, path) { return current; } -/** - * Checks if a nested path exists in an object - */ -export function hasNestedPath(obj, path) { - const value = getNestedValue(obj, path); - return value !== undefined; -} - /** * Removes keys that should be skipped from an object */ @@ -430,6 +422,8 @@ export function filterSkippedMethods(methods) { return methods.filter((method) => { const methodName = method?.name; if (!methodName) return true; - return !shouldSkipMethod(methodName); + + const category = getSkippedMethodCategory(methodName); + return category !== 'discarded'; }); } From 2a7a15dbad95dc10d78dba7d154707c73b56de76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Tue, 17 Jun 2025 12:52:49 +0200 Subject: [PATCH 16/17] chore: update OpenRPC JSON updater config to include `eth_getStorageAt.params.2.required` in CUSTOM_FIELDS (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js index 84d865e729..160840202a 100644 --- a/scripts/openrpc-json-updater/config.js +++ b/scripts/openrpc-json-updater/config.js @@ -12,6 +12,7 @@ export const CUSTOM_FIELDS = [ '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_*']; From 388ccab03390da8621dbe7fb08bb6e7961798afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walczak?= Date: Wed, 25 Jun 2025 14:07:17 +0200 Subject: [PATCH 17/17] chore: update OpenRPC JSON updater config (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Walczak --- scripts/openrpc-json-updater/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/openrpc-json-updater/config.js b/scripts/openrpc-json-updater/config.js index 160840202a..5e7f310825 100644 --- a/scripts/openrpc-json-updater/config.js +++ b/scripts/openrpc-json-updater/config.js @@ -12,7 +12,7 @@ export const CUSTOM_FIELDS = [ 'eth_feeHistory.result.schema.properties.reward.title', 'eth_getTransactionCount.summary', 'eth_maxPriorityFeePerGas.result.schema.description', - 'eth_getStorageAt.params.2.required', + 'eth_sendRawTransaction.summary', ]; export const DISCARDED_METHODS = ['engine_*'];