From f1a00ad9888f5487c3463ccc15a6bc2595534599 Mon Sep 17 00:00:00 2001 From: Marc Hanson Date: Mon, 7 Apr 2025 00:44:16 -0400 Subject: [PATCH 1/7] Update deps --- package-lock.json | 32 ++++++++++++++++---------------- package.json | 8 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71f341dd..d3b2fc5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@shardeum-foundation/json-rpc-server", - "version": "1.1.0-prerelease.9", + "version": "1.2.0-prerelease.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@shardeum-foundation/json-rpc-server", - "version": "1.1.0-prerelease.9", + "version": "1.2.0-prerelease.0", "license": "ISC", "dependencies": { "@ethereumjs/rlp": "5.0.2", @@ -14,9 +14,9 @@ "@ethereumjs/util": "9.0.2", "@ethersproject/bignumber": "5.7.0", "@hapi/sntp": "4.0.0", - "@shardeum-foundation/lib-archiver-discovery": "1.2.0-prerelease.4", - "@shardeum-foundation/lib-crypto-utils": "4.2.0-prerelease.4", - "@shardeum-foundation/lib-types": "1.3.0-prerelease.3", + "@shardeum-foundation/lib-archiver-discovery": "1.3.0-prerelease.1", + "@shardeum-foundation/lib-crypto-utils": "4.3.0-prerelease.1", + "@shardeum-foundation/lib-types": "1.4.0-prerelease.2", "axios": "1.6.1", "better-sqlite3": "7.6.2", "body-parser": "1.19.0", @@ -1684,21 +1684,21 @@ } }, "node_modules/@shardeum-foundation/lib-archiver-discovery": { - "version": "1.2.0-prerelease.4", - "resolved": "https://registry.npmjs.org/@shardeum-foundation/lib-archiver-discovery/-/lib-archiver-discovery-1.2.0-prerelease.4.tgz", - "integrity": "sha512-eZFNEdNLzRE3ckxSxyomLrH26VhwcLt5ZEo1SLxxE8GKOF4MwKCyZxfRYHOtdjssINcFBrLVGJtlcE8PKEGksA==", + "version": "1.3.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@shardeum-foundation/lib-archiver-discovery/-/lib-archiver-discovery-1.3.0-prerelease.1.tgz", + "integrity": "sha512-7FxjyYHV23pziozr0zvL+U0NZDJC8T/EjIlWqtG/7o31sItPOTghjFBqlNTfKlLn2emR3xoufd6vi3GnuY/LlA==", "dependencies": { - "@shardeum-foundation/lib-crypto-utils": "4.2.0-prerelease.4", + "@shardeum-foundation/lib-crypto-utils": "4.3.0-prerelease.1", "axios": "1.6.1", "gts": "3.1.1" } }, "node_modules/@shardeum-foundation/lib-crypto-utils": { - "version": "4.2.0-prerelease.4", - "resolved": "https://registry.npmjs.org/@shardeum-foundation/lib-crypto-utils/-/lib-crypto-utils-4.2.0-prerelease.4.tgz", - "integrity": "sha512-+VL1W15E/lya6lmk50hlZUT5D1TcePwm7vGfledAcpNCIJtoYxA4w9f3WxVrZ6FMamt1wLbKTzkLZPaQBcSSOA==", + "version": "4.3.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@shardeum-foundation/lib-crypto-utils/-/lib-crypto-utils-4.3.0-prerelease.1.tgz", + "integrity": "sha512-XVOogbJKSSSyQ7V9bcmsTvBIZjEDlRALepOSYXbBu8/WKlv19KCieQ6dMtjihFK01r/WWqohT+afTKxyym7++A==", "dependencies": { - "@shardeum-foundation/lib-types": "1.3.0-prerelease.3", + "@shardeum-foundation/lib-types": "1.4.0-prerelease.2", "buffer-xor": "2.0.2", "fast-stable-stringify": "1.0.0", "sodium-native": "4.3.1" @@ -1708,9 +1708,9 @@ } }, "node_modules/@shardeum-foundation/lib-types": { - "version": "1.3.0-prerelease.3", - "resolved": "https://registry.npmjs.org/@shardeum-foundation/lib-types/-/lib-types-1.3.0-prerelease.3.tgz", - "integrity": "sha512-wOtMQsCOtasGqOhg8CQoPMo7iUnByv9X65a5JSS++bHqtl3quv5fCqYAQq6vpluvHt5xaamlmOuIhEsKck8lbg==" + "version": "1.4.0-prerelease.2", + "resolved": "https://registry.npmjs.org/@shardeum-foundation/lib-types/-/lib-types-1.4.0-prerelease.2.tgz", + "integrity": "sha512-C0gd4vwTPTkfembvMk5hYBtSssZG6tKzJcKEI7r+8CsSL38T326yPmZbbsRRILSDwfcn5ebZk+te4Stb3mOBAQ==" }, "node_modules/@sinclair/typebox": { "version": "0.27.8", diff --git a/package.json b/package.json index 68303bdd..d46a61d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shardeum-foundation/json-rpc-server", - "version": "1.1.0-prerelease.9", + "version": "1.2.0-prerelease.0", "description": "JSON RPC server for Shardeum", "main": "src/server.js", "scripts": { @@ -27,9 +27,9 @@ "author": "thantsintoe", "license": "ISC", "dependencies": { - "@shardeum-foundation/lib-archiver-discovery": "1.2.0-prerelease.4", - "@shardeum-foundation/lib-crypto-utils": "4.2.0-prerelease.4", - "@shardeum-foundation/lib-types": "1.3.0-prerelease.3", + "@shardeum-foundation/lib-archiver-discovery": "1.3.0-prerelease.1", + "@shardeum-foundation/lib-crypto-utils": "4.3.0-prerelease.1", + "@shardeum-foundation/lib-types": "1.4.0-prerelease.2", "@ethereumjs/rlp": "5.0.2", "@ethereumjs/tx": "3.4.0", "@ethereumjs/util": "9.0.2", From 83acd8541fbbd9658b49c79aa9f709800536c8ce Mon Sep 17 00:00:00 2001 From: Marc Hanson Date: Mon, 7 Apr 2025 00:44:43 -0400 Subject: [PATCH 2/7] 1.2.0-prerelease.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d3b2fc5a..bb4d65ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@shardeum-foundation/json-rpc-server", - "version": "1.2.0-prerelease.0", + "version": "1.2.0-prerelease.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@shardeum-foundation/json-rpc-server", - "version": "1.2.0-prerelease.0", + "version": "1.2.0-prerelease.1", "license": "ISC", "dependencies": { "@ethereumjs/rlp": "5.0.2", diff --git a/package.json b/package.json index d46a61d8..20f610dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shardeum-foundation/json-rpc-server", - "version": "1.2.0-prerelease.0", + "version": "1.2.0-prerelease.1", "description": "JSON RPC server for Shardeum", "main": "src/server.js", "scripts": { From c4bc6c9e14aefd928eab50120e4fb6c5af16f967 Mon Sep 17 00:00:00 2001 From: Ungter Date: Fri, 18 Apr 2025 01:56:24 -0400 Subject: [PATCH 3/7] Refactor duplicate api methods sample --- package-lock.json | 10 +- package.json | 2 +- src/api.ts | 244 ++++++++++++++++++++++++++++++---------------- 3 files changed, 165 insertions(+), 91 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb4d65ac..654982f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@shardeum-foundation/lib-crypto-utils": "4.3.0-prerelease.1", "@shardeum-foundation/lib-types": "1.4.0-prerelease.2", "axios": "1.6.1", - "better-sqlite3": "7.6.2", + "better-sqlite3": "^9.0.0", "body-parser": "1.19.0", "connect": "3.7.0", "cookie-parser": "1.4.6", @@ -3021,13 +3021,13 @@ } }, "node_modules/better-sqlite3": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.6.2.tgz", - "integrity": "sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz", + "integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==", "hasInstallScript": true, "dependencies": { "bindings": "^1.5.0", - "prebuild-install": "^7.1.0" + "prebuild-install": "^7.1.1" } }, "node_modules/binary-extensions": { diff --git a/package.json b/package.json index 20f610dd..3f61f07a 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@ethersproject/bignumber": "5.7.0", "@hapi/sntp": "4.0.0", "axios": "1.6.1", - "better-sqlite3": "7.6.2", + "better-sqlite3": "^9.0.0", "body-parser": "1.19.0", "connect": "3.7.0", "cookie-parser": "1.4.6", diff --git a/src/api.ts b/src/api.ts index 9620d9f3..ba4b5157 100755 --- a/src/api.ts +++ b/src/api.ts @@ -53,6 +53,7 @@ import { archiverAPI } from './external/Archiver' import { TTLMap } from './utils/TTLMap' import { buildGetTransactionByBlockHashAndIndex } from './eth-handlers/eth_getTransactionByBlockHashAndIndex' import { buildGetTransactionByBlockNumberAndIndex } from './eth-handlers/eth_getTransactionByBlockNumberAndIndex' +import { performance } from 'perf_hooks' export const verbose = config.verbose export const firstLineLogs = config.firstLineLogs @@ -813,7 +814,11 @@ function trimInjectRejection(message: string): string { return 'ECONNREFUSED' } else return message } -async function validateBlockNumberInput(blockNumberInput: string) { +async function validateBlockNumberInput(blockNumberInput: string | undefined) { + // Handle undefined input first + if (blockNumberInput === undefined) { + return undefined + } // If the block number is 'latest', return undefined, so that it will get latest balance if (blockNumberInput === 'latest') { return undefined @@ -828,6 +833,73 @@ async function validateBlockNumberInput(blockNumberInput: string) { return blockNumberInput } +/** + * Wraps an API method handler with common logic for: + * - Creating a unique ticket ID for tracking. + * - Counting the endpoint event. + * - Emitting start and end log events. + * - Basic argument validation (checking if args is an array). + * - Basic error handling framework. + * + * @param methodName - The name of the API method (e.g., 'eth_getBalance'). + * @param handler - The core async function implementing the method's specific logic. + * It receives the validated arguments (as an array) and the JSON RPC callback. + * @param validateArgs - Optional function to perform specific argument validation. + * Should return true if args are valid, false otherwise. + * If it returns false, it's responsible for calling the callback with an error. + */ +function wrapApiMethod( + methodName: string, + handler: (ticket: string, args: TArgs, callback: JSONRPCCallbackTypePlain) => Promise, + validateArgs?: (args: TArgs, callback: JSONRPCCallbackTypePlain) => boolean +) { + return async (requestArgs: RequestParamsLike, callback: JSONRPCCallbackTypePlain): Promise => { + nestedCountersInstance.countEvent('endpoint', methodName); + + if (!ensureArrayArgs(requestArgs, callback)) { + countFailedResponse(methodName, 'Invalid params: non-array args'); + return; + } + + const args = requestArgs as TArgs; // Cast after validation + + // Perform custom validation if provided + if (validateArgs && !validateArgs(args, callback)) { + // The validator is responsible for calling callback with error and counting failure + // logEventEmitter emission for fn_end should ideally happen within the validator on error + return; + } + + const ticket = crypto + .createHash('sha1') + .update(methodName + Math.random() + Date.now()) + .digest('hex'); + + logEventEmitter.emit('fn_start', ticket, methodName, performance.now()); + /* prettier-ignore */ if (firstLineLogs) { console.log(`Running ${methodName}`, args); } + + try { + await handler(ticket, args, callback); + // Assuming the handler calls callback on success and emits fn_end appropriately + // If the handler throws, the catch block will handle it. + } catch (error: any) { + console.error(`Error in ${methodName}:`, error); + const nodeUrl = error?.nodeUrl // Attempt to extract nodeUrl if available in error + logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: false, error: error.message || 'Unknown error' }, performance.now()); + // Use a generic error structure if the specific handler didn't provide one + const jsonError: JSONRPCError = { + code: error.code || -32603, // Internal error + message: error.message || `Internal error executing ${methodName}`, + }; + callback(jsonError, null); + countFailedResponse(methodName, `Exception: ${error.message || 'Unknown error'}`); + } + // Note: fn_end for successful cases should be emitted within the specific handler + // before calling the callback, as the success state and potential metadata (like nodeUrl) + // are only known there. The finally block here might be too late or lack context. + }; +} + export const methods = { web3_clientVersion: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { const api_name = 'web3_clientVersion' @@ -1059,95 +1131,97 @@ export const methods = { countSuccessResponse(api_name, 'success', 'validator') } }, - eth_getBalance: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'eth_getBalance' - nestedCountersInstance.countEvent('endpoint', api_name) - if (!ensureArrayArgs(args, callback)) { - countFailedResponse(api_name, 'Invalid params: non-array args') - return - } - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running eth_getBalance', args) } + eth_getBalance: wrapApiMethod( + 'eth_getBalance', + async (ticket, args, callback) => { + let address: string; + let blockNumberInput: string | undefined; + try { + address = args[0]; + blockNumberInput = args[1]; // Already validated as array, args[1] might be undefined + } catch (e) { + // This catch might be less likely now with prior array validation, but keep for safety + if (verbose) console.log('Error parsing arguments for eth_getBalance', e); + logEventEmitter.emit('fn_end', ticket, { success: false, error: 'Error parsing arguments' }, performance.now()); + callback({ code: -32602, message: 'Invalid arguments for eth_getBalance' }, null); + countFailedResponse('eth_getBalance', 'Error parsing arguments'); + return; + } - let address - let blockNumber - try { - address = args[0] - blockNumber = args[1] || undefined - } catch (e) { - if (verbose) console.log('Unable to get address', e) - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback({ code: -32000, message: 'Unable to get address' }, null) - countFailedResponse(api_name, 'Unable to get address') - return - } - if (!isValidAddress(address)) { - if (verbose) console.log('Invalid address', address) - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback({ code: -32000, message: 'Invalid address' }, null) - countFailedResponse(api_name, 'Invalid address') - return - } - // validate input blockNumber that support text such 'latest', 'earliest' ... - blockNumber = await validateBlockNumberInput(blockNumber) - let balance - try { - balance = await serviceValidator.getBalance(address, blockNumber) - if (balance) { - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, intStringToHex(balance)) - countSuccessResponse(api_name, 'success', 'serviceValidator') - return + if (!isValidAddress(address)) { + if (verbose) console.log('Invalid address', address); + logEventEmitter.emit('fn_end', ticket, { success: false, error: 'Invalid address' }, performance.now()); + callback({ code: -32000, message: 'Invalid address' }, null); + countFailedResponse('eth_getBalance', 'Invalid address'); + return; } - } catch (e) { - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback({ code: 503, message: 'unable to get balanace' }, null) - countFailedResponse(api_name, 'Unable to get balance') - return - } - balance = '0x0' - let nodeUrl - try { - if (verbose) console.log('address', address) - if (verbose) console.log('ETH balance', typeof balance, balance) - const res = await getAccountFromValidator(address) - nodeUrl = res.nodeUrl - if ('account' in res) { - const account = res.account - if (verbose) console.log('account', account) - if (!account) { - // This covers the case where this is an uninitialized EOA - // and our validators return { account: null } - // hence returning balance as 0x0 - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, balance) - countSuccessResponse(api_name, 'success', 'validator') - } else { - if (verbose) console.log('Shardeum balance', typeof account.balance, account.balance) - const balance = intStringToHex(account.balance) - if (verbose) console.log('SHD', typeof balance, balance) - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: true }, performance.now()) - callback(null, balance) - countSuccessResponse(api_name, 'success', 'validator') + // validate input blockNumber that support text such 'latest', 'earliest' ... + // Passing undefined is fine if blockNumberInput is undefined + const blockNumber = await validateBlockNumberInput(blockNumberInput); + let balance: string | null; + let nodeUrl: string | undefined; // Define nodeUrl here + + try { + balance = await serviceValidator.getBalance(address, blockNumber); + if (balance !== null) { // Check specifically for non-null + logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()); + callback(null, intStringToHex(balance)); + countSuccessResponse('eth_getBalance', 'success', 'serviceValidator'); + return; } - } else { - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: false }, performance.now()) - callback({ code: 503, message: 'unable to get balanace' }, null) - countFailedResponse(api_name, 'Unable to get account') + // If balance is null, proceed to validator fallback + } catch (e: any) { // Catch errors from serviceValidator + console.error('Error getting balance from serviceValidator:', e); + // Don't return immediately, try fallback + // We might want to log this error but still attempt the fallback } - } catch (e) { - // if (verbose) console.log('Unable to get account balance', e) - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: false }, performance.now()) - callback({ code: 503, message: 'unable to get balanace' }, null) - countFailedResponse(api_name, 'Unable to get balance from validator') - } - if (verbose) console.log('Final balance', balance) - }, + + // Fallback to getAccountFromValidator + balance = '0x0'; // Default balance + try { + if (verbose) console.log('Attempting fallback: getAccountFromValidator for address', address); + const res = await getAccountFromValidator(address); + nodeUrl = res.nodeUrl; // Capture nodeUrl from the response + + if ('account' in res && res.account) { + if (verbose) console.log('Validator account found:', res.account); + balance = intStringToHex(res.account.balance); + logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: true }, performance.now()); + callback(null, balance); + countSuccessResponse('eth_getBalance', 'success', 'validator'); + } else if ('account' in res && res.account === null) { + // Covers uninitialized EOA where validator returns { account: null } + if (verbose) console.log('Validator returned null account (uninitialized EOA?) for address:', address); + logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: true }, performance.now()); + callback(null, '0x0'); // Return '0x0' as balance + countSuccessResponse('eth_getBalance', 'success (null account)', 'validator'); + } else { + // Case where 'account' key is missing or other unexpected structure + if (verbose) console.log('Unable to get account from validator, response structure:', res) + logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: false, error: 'Unable to get account' }, performance.now()); + callback({ code: 503, message: 'Unable to get balance (validator account fetch failed)' }, null); + countFailedResponse('eth_getBalance', 'Unable to get account from validator'); + } + } catch (e: any) { + console.error('Error getting balance from validator fallback:', e); + logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: false, error: e.message || 'Validator fallback failed' }, performance.now()); + callback({ code: 503, message: 'Unable to get balance (validator fallback exception)' }, null); + countFailedResponse('eth_getBalance', `Exception during validator fallback: ${e.message || 'Unknown error'}`); + } + if (verbose) console.log('Final balance returned', balance); + } + // Optional validator for eth_getBalance (can be expanded) + // (args, callback) => { + // if (!args || args.length < 1 || typeof args[0] !== 'string') { + // callback({ code: -32602, message: 'Invalid params: address missing or not a string' }); + // countFailedResponse('eth_getBalance', 'Invalid params: address missing or not a string'); + // logEventEmitter.emit('fn_end', 'N/A', { success: false, error: 'Invalid params' }, performance.now()); // Ticket might not be generated yet + // return false; + // } + // return true; + // } + ), eth_getStorageAt: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { const api_name = 'eth_getStorageAt' nestedCountersInstance.countEvent('endpoint', api_name) From 253247f725683e68c9baff0c6a4f415cbe005cc1 Mon Sep 17 00:00:00 2001 From: Ungter Date: Fri, 18 Apr 2025 03:18:26 -0400 Subject: [PATCH 4/7] updated wrapper and examples --- src/api.ts | 545 ++++++++++++++++++----------------------------------- 1 file changed, 184 insertions(+), 361 deletions(-) diff --git a/src/api.ts b/src/api.ts index ba4b5157..4c27e236 100755 --- a/src/api.ts +++ b/src/api.ts @@ -815,7 +815,6 @@ function trimInjectRejection(message: string): string { } else return message } async function validateBlockNumberInput(blockNumberInput: string | undefined) { - // Handle undefined input first if (blockNumberInput === undefined) { return undefined } @@ -833,394 +832,218 @@ async function validateBlockNumberInput(blockNumberInput: string | undefined) { return blockNumberInput } -/** - * Wraps an API method handler with common logic for: - * - Creating a unique ticket ID for tracking. - * - Counting the endpoint event. - * - Emitting start and end log events. - * - Basic argument validation (checking if args is an array). - * - Basic error handling framework. - * - * @param methodName - The name of the API method (e.g., 'eth_getBalance'). - * @param handler - The core async function implementing the method's specific logic. - * It receives the validated arguments (as an array) and the JSON RPC callback. - * @param validateArgs - Optional function to perform specific argument validation. - * Should return true if args are valid, false otherwise. - * If it returns false, it's responsible for calling the callback with an error. - */ -function wrapApiMethod( + +type HandlerMeta = { nodeUrl?: string; source?: string }; +type WrappedHandler = + (ticket: string, args: T, cb: JSONRPCCallbackTypePlain) => Promise; + +export function wrapApiMethod( methodName: string, - handler: (ticket: string, args: TArgs, callback: JSONRPCCallbackTypePlain) => Promise, - validateArgs?: (args: TArgs, callback: JSONRPCCallbackTypePlain) => boolean + handler: WrappedHandler, + validateArgs?: (args: T, cb: JSONRPCCallbackTypePlain) => boolean ) { - return async (requestArgs: RequestParamsLike, callback: JSONRPCCallbackTypePlain): Promise => { + return async (requestArgs: RequestParamsLike, cb: JSONRPCCallbackTypePlain) => { + // pre‑flight nestedCountersInstance.countEvent('endpoint', methodName); - if (!ensureArrayArgs(requestArgs, callback)) { - countFailedResponse(methodName, 'Invalid params: non-array args'); - return; - } - - const args = requestArgs as TArgs; // Cast after validation + // Treat “no params” as an empty list so zero‑arg methods succeed. + const args = (requestArgs ?? []) as T; - // Perform custom validation if provided - if (validateArgs && !validateArgs(args, callback)) { - // The validator is responsible for calling callback with error and counting failure - // logEventEmitter emission for fn_end should ideally happen within the validator on error + if (!ensureArrayArgs(args, cb)) { // still rejects if params isn’t an array + countFailedResponse(methodName, 'Invalid params: non‑array args'); return; } + if (validateArgs && !validateArgs(args, cb)) return; - const ticket = crypto - .createHash('sha1') - .update(methodName + Math.random() + Date.now()) - .digest('hex'); + // tracing start + const ticket = crypto.createHash('sha1') + .update(methodName + Math.random() + Date.now()) + .digest('hex'); + const t0 = performance.now(); + logEventEmitter.emit('fn_start', ticket, methodName, t0); + /* prettier‑ignore */ if (firstLineLogs) { console.log(`Running ${methodName}`, args); } - logEventEmitter.emit('fn_start', ticket, methodName, performance.now()); - /* prettier-ignore */ if (firstLineLogs) { console.log(`Running ${methodName}`, args); } + // execute + let meta: HandlerMeta | undefined; + let failed = false; + let failMsg = ''; try { - await handler(ticket, args, callback); - // Assuming the handler calls callback on success and emits fn_end appropriately - // If the handler throws, the catch block will handle it. - } catch (error: any) { - console.error(`Error in ${methodName}:`, error); - const nodeUrl = error?.nodeUrl // Attempt to extract nodeUrl if available in error - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: false, error: error.message || 'Unknown error' }, performance.now()); - // Use a generic error structure if the specific handler didn't provide one - const jsonError: JSONRPCError = { - code: error.code || -32603, // Internal error - message: error.message || `Internal error executing ${methodName}`, - }; - callback(jsonError, null); - countFailedResponse(methodName, `Exception: ${error.message || 'Unknown error'}`); - } - // Note: fn_end for successful cases should be emitted within the specific handler - // before calling the callback, as the success state and potential metadata (like nodeUrl) - // are only known there. The finally block here might be too late or lack context. + meta = await handler(ticket, args, cb) ?? undefined; + countSuccessResponse(methodName, 'success', meta?.source ?? 'handler'); + } catch (err: any) { + failed = true; + failMsg = err?.message ?? 'Internal error'; + + countFailedResponse(methodName, failMsg); + cb({ code: err?.code ?? -32603, message: failMsg }, null); + } finally { + logEventEmitter.emit( + 'fn_end', + ticket, + { success: !failed, ...(meta ?? {}), ...(failed ? { error: failMsg } : {}) }, + performance.now() + ); + } }; } -export const methods = { - web3_clientVersion: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'web3_clientVersion' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - - /* prettier-ignore */ if (firstLineLogs) { console.log('Running web3_clientVersion', args) } - /* prettier-ignore */ if (firstLineLogs) { console.log('Running getCurrentBlockInfo', args) } - const result = 'Mist/v0.9.3/darwin/go1.4.1' - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success') - }, - web3_sha3: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'web3_sha3' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - - /* prettier-ignore */ if (firstLineLogs) { console.log('Running web3_sha3', args) } - const result = '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad' - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success') - }, - net_version: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'net_version' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - - /* prettier-ignore */ if (firstLineLogs) { console.log('Running net_version', args) } - const chainId = config.chainId.toString() - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, chainId) - countSuccessResponse(api_name, 'success') - }, - net_listening: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'net_listening' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running net_listening', args) } - const result = true - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success') - }, - net_peerCount: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'net_peerCount' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running net_peerCount', args) } - const result = '0x2' - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success') - }, - eth_protocolVersion: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'eth_protocolVersion' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running eth_protocolVersion', args) } - const result = '54' - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success') - }, - eth_syncing: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'eth_syncing' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running eth_syncing', args) } - // RPC talks only to active nodes, so result is always false. - const result = false - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success') - }, - eth_coinbase: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'eth_coinbase' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running eth_coinbase', args) } - const result = '' - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success') - }, - eth_mining: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'eth_mining' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running eth_mining', args) } - const result = true - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success') - }, - eth_hashrate: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'eth_hashrate' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running eth_hashrate', args) } - const result = '0x38a' - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success') - }, - eth_gasPrice: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'eth_gasPrice' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running eth_gasPrice', args) } - - const gasPrice = await serviceValidator.getGasPrice() - if (gasPrice) { - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, gasPrice) - countSuccessResponse(api_name, 'success', 'serviceValidator') - return +export const methods = { + web3_clientVersion: wrapApiMethod( + 'web3_clientVersion', + async (_ticket, _args, cb) => { + cb(null, 'Mist/v0.9.3/darwin/go1.4.1') } - - const fallbackGasPrice = '0x3f84fc7516' // 1 Gwei - try { - const { result } = await getGasPrice() - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success', 'TBD') - return - } catch (e) { - console.log('Unable to get gas price', e) + ), + web3_sha3: wrapApiMethod( + 'web3_sha3', + async (_ticket, _args, cb) => { + cb(null, '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad') } - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, fallbackGasPrice) - countSuccessResponse(api_name, 'success fallback', 'TBD') - }, - eth_accounts: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'eth_accounts' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running eth_accounts', args) } - const result: string[] = [] - - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, result) - countSuccessResponse(api_name, 'success', 'TBD') - }, - eth_blockNumber: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { - const api_name = 'eth_blockNumber' - nestedCountersInstance.countEvent('endpoint', api_name) - const ticket = crypto - .createHash('sha1') - .update(api_name + Math.random() + Date.now()) - .digest('hex') - logEventEmitter.emit('fn_start', ticket, api_name, performance.now()) - /* prettier-ignore */ if (firstLineLogs) { console.log('Running eth_blockNumber', args) } - const result = await collectorAPI.getLatestBlockNumber() - if (result) { - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()) - callback(null, '0x' + result.number.toString(16)) - countSuccessResponse(api_name, 'success', 'collector') - return + ), + net_version: wrapApiMethod( + 'net_version', + async (_ticket, _args, cb) => { + cb(null, config.chainId.toString()) } - const { blockNumber, nodeUrl } = await getCurrentBlockInfo() - if (verbose) console.log('BLOCK NUMBER', blockNumber, parseInt(blockNumber, 16)) - if (blockNumber == null) { - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: true }, performance.now()) - callback(null, '0x0') - countFailedResponse(api_name, 'blockNumber is null') - } else { - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: true }, performance.now()) - callback(null, blockNumber) - countSuccessResponse(api_name, 'success', 'validator') + ), + net_listening: wrapApiMethod( + 'net_listening', + async (_ticket, _args, cb) => { + cb(null, true) } - }, + ), + net_peerCount: wrapApiMethod( + 'net_peerCount', + async (_ticket, _args, cb) => { + cb(null, '0x2') + } + ), + eth_protocolVersion: wrapApiMethod( + 'eth_protocolVersion', + async (_t, _args, cb) => { + cb(null, '54') + } + ), + eth_syncing: wrapApiMethod( + 'eth_syncing', + async (_t, _args, cb) => { + cb(null, false) + } + ), + eth_coinbase: wrapApiMethod( + 'eth_coinbase', + async (_t, _args, cb) => { + cb(null, '') + } + ), + eth_mining: wrapApiMethod( + 'eth_mining', + async (_t, _args, cb) => { + cb(null, true) + } + ), + eth_hashrate: wrapApiMethod( + 'eth_hashrate', + async (_t, _args, cb) => { + cb(null, '0x38a') + } + ), + eth_gasPrice: wrapApiMethod( + 'eth_gasPrice', + async (_t, _args, cb) => { + // primary source – serviceValidator + const gasPrice = await serviceValidator.getGasPrice(); + if (gasPrice) { + cb(null, gasPrice); + return { source: 'serviceValidator' }; + } + + // fallback RPC + try { + const { result } = await getGasPrice(); + cb(null, result); + return { source: 'rpc' }; + } catch { + // fall through to static fallback + } + + // static fallback: 1 Gwei + const fallback = '0x3f84fc7516'; + cb(null, fallback); + return { source: 'static‑fallback' }; + } + ), + eth_accounts: wrapApiMethod( + 'eth_accounts', + async (_t, _args, cb) => { + cb(null, [] as string[]); + } + ), + eth_blockNumber: wrapApiMethod( + 'eth_blockNumber', + async (_t, _args, cb) => { + // collector + const latest = await collectorAPI.getLatestBlockNumber(); + if (latest) { + cb(null, '0x' + latest.number.toString(16)); + return { source: 'collector' }; + } + + // validator fallback + const { blockNumber, nodeUrl } = await getCurrentBlockInfo(); + const value = blockNumber ?? '0x0'; // keep original behaviour + cb(null, value); + return { nodeUrl, source: 'validator', note: blockNumber ? 'success' : 'null‑fallback' }; + } + ), eth_getBalance: wrapApiMethod( 'eth_getBalance', - async (ticket, args, callback) => { - let address: string; - let blockNumberInput: string | undefined; - try { - address = args[0]; - blockNumberInput = args[1]; // Already validated as array, args[1] might be undefined - } catch (e) { - // This catch might be less likely now with prior array validation, but keep for safety - if (verbose) console.log('Error parsing arguments for eth_getBalance', e); - logEventEmitter.emit('fn_end', ticket, { success: false, error: 'Error parsing arguments' }, performance.now()); - callback({ code: -32602, message: 'Invalid arguments for eth_getBalance' }, null); - countFailedResponse('eth_getBalance', 'Error parsing arguments'); - return; + + async (_ticket, [address, rawBlock]: [string | undefined, string?], cb) => { + if (address == null) { + throw { code: -32000, message: 'Unable to get address' }; } - if (!isValidAddress(address)) { - if (verbose) console.log('Invalid address', address); - logEventEmitter.emit('fn_end', ticket, { success: false, error: 'Invalid address' }, performance.now()); - callback({ code: -32000, message: 'Invalid address' }, null); - countFailedResponse('eth_getBalance', 'Invalid address'); - return; + throw { code: -32000, message: 'Invalid address' }; } - - // validate input blockNumber that support text such 'latest', 'earliest' ... - // Passing undefined is fine if blockNumberInput is undefined - const blockNumber = await validateBlockNumberInput(blockNumberInput); - let balance: string | null; - let nodeUrl: string | undefined; // Define nodeUrl here - + + const blockNumber = await validateBlockNumberInput(rawBlock); + + // primary source try { - balance = await serviceValidator.getBalance(address, blockNumber); - if (balance !== null) { // Check specifically for non-null - logEventEmitter.emit('fn_end', ticket, { success: true }, performance.now()); - callback(null, intStringToHex(balance)); - countSuccessResponse('eth_getBalance', 'success', 'serviceValidator'); - return; + const balance = await serviceValidator.getBalance(address, blockNumber); + if (balance != null) { + cb(null, intStringToHex(balance)); + return { source: 'serviceValidator' }; } - // If balance is null, proceed to validator fallback - } catch (e: any) { // Catch errors from serviceValidator - console.error('Error getting balance from serviceValidator:', e); - // Don't return immediately, try fallback - // We might want to log this error but still attempt the fallback + } catch { + throw { code: 503, message: 'Unable to get balance' }; } - - // Fallback to getAccountFromValidator - balance = '0x0'; // Default balance + + // fallback: validator node + let res; try { - if (verbose) console.log('Attempting fallback: getAccountFromValidator for address', address); - const res = await getAccountFromValidator(address); - nodeUrl = res.nodeUrl; // Capture nodeUrl from the response - - if ('account' in res && res.account) { - if (verbose) console.log('Validator account found:', res.account); - balance = intStringToHex(res.account.balance); - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: true }, performance.now()); - callback(null, balance); - countSuccessResponse('eth_getBalance', 'success', 'validator'); - } else if ('account' in res && res.account === null) { - // Covers uninitialized EOA where validator returns { account: null } - if (verbose) console.log('Validator returned null account (uninitialized EOA?) for address:', address); - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: true }, performance.now()); - callback(null, '0x0'); // Return '0x0' as balance - countSuccessResponse('eth_getBalance', 'success (null account)', 'validator'); - } else { - // Case where 'account' key is missing or other unexpected structure - if (verbose) console.log('Unable to get account from validator, response structure:', res) - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: false, error: 'Unable to get account' }, performance.now()); - callback({ code: 503, message: 'Unable to get balance (validator account fetch failed)' }, null); - countFailedResponse('eth_getBalance', 'Unable to get account from validator'); - } - } catch (e: any) { - console.error('Error getting balance from validator fallback:', e); - logEventEmitter.emit('fn_end', ticket, { nodeUrl, success: false, error: e.message || 'Validator fallback failed' }, performance.now()); - callback({ code: 503, message: 'Unable to get balance (validator fallback exception)' }, null); - countFailedResponse('eth_getBalance', `Exception during validator fallback: ${e.message || 'Unknown error'}`); - } - if (verbose) console.log('Final balance returned', balance); - } - // Optional validator for eth_getBalance (can be expanded) - // (args, callback) => { - // if (!args || args.length < 1 || typeof args[0] !== 'string') { - // callback({ code: -32602, message: 'Invalid params: address missing or not a string' }); - // countFailedResponse('eth_getBalance', 'Invalid params: address missing or not a string'); - // logEventEmitter.emit('fn_end', 'N/A', { success: false, error: 'Invalid params' }, performance.now()); // Ticket might not be generated yet - // return false; - // } - // return true; - // } + res = await getAccountFromValidator(address); + } catch { + throw { code: 503, message: 'Unable to get balance from validator' }; + } + + const nodeUrl = res.nodeUrl; + if (!('account' in res)) { + throw { code: 503, message: 'Unable to get account' }; + } + + const account = res.account; + const balanceHex = + account && account.balance != null + ? intStringToHex(account.balance) + : '0x0'; + + cb(null, balanceHex); + return { nodeUrl, source: 'validator' }; + }, ), eth_getStorageAt: async function (args: RequestParamsLike, callback: JSONRPCCallbackTypePlain) { const api_name = 'eth_getStorageAt' From cef42b36b1bcef00e7f58cc955218b76659e3799 Mon Sep 17 00:00:00 2001 From: Ungter Date: Fri, 18 Apr 2025 03:56:45 -0400 Subject: [PATCH 5/7] added description to wrapper --- src/api.ts | 66 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/api.ts b/src/api.ts index 4c27e236..d27eed09 100755 --- a/src/api.ts +++ b/src/api.ts @@ -832,10 +832,15 @@ async function validateBlockNumberInput(blockNumberInput: string | undefined) { return blockNumberInput } - -type HandlerMeta = { nodeUrl?: string; source?: string }; +/** + * - Call `cb(null, data)` on success ONCE, then `return` optional metadata + * - Do not call `cb()` on failure, use `throw { code(optional), message }` + * - the wrapper will catch the error, call `cb(error, null)`, + * call `countFailedResponse()`, and mark fn_end as `success: false` + */ +type HandlerMeta = { nodeUrl?: string; source?: string } type WrappedHandler = - (ticket: string, args: T, cb: JSONRPCCallbackTypePlain) => Promise; + (ticket: string, args: T, cb: JSONRPCCallbackTypePlain) => Promise export function wrapApiMethod( methodName: string, @@ -844,39 +849,39 @@ export function wrapApiMethod( ) { return async (requestArgs: RequestParamsLike, cb: JSONRPCCallbackTypePlain) => { // pre‑flight - nestedCountersInstance.countEvent('endpoint', methodName); + nestedCountersInstance.countEvent('endpoint', methodName) // Treat “no params” as an empty list so zero‑arg methods succeed. - const args = (requestArgs ?? []) as T; + const args = (requestArgs ?? []) as T if (!ensureArrayArgs(args, cb)) { // still rejects if params isn’t an array - countFailedResponse(methodName, 'Invalid params: non‑array args'); + countFailedResponse(methodName, 'Invalid params: non‑array args') return; } - if (validateArgs && !validateArgs(args, cb)) return; + if (validateArgs && !validateArgs(args, cb)) return // tracing start const ticket = crypto.createHash('sha1') .update(methodName + Math.random() + Date.now()) - .digest('hex'); - const t0 = performance.now(); - logEventEmitter.emit('fn_start', ticket, methodName, t0); - /* prettier‑ignore */ if (firstLineLogs) { console.log(`Running ${methodName}`, args); } + .digest('hex') + const t0 = performance.now() + logEventEmitter.emit('fn_start', ticket, methodName, t0) + /* prettier‑ignore */ if (firstLineLogs) { console.log(`Running ${methodName}`, args) } // execute - let meta: HandlerMeta | undefined; - let failed = false; - let failMsg = ''; + let meta: HandlerMeta | undefined + let failed = false + let failMsg = '' try { - meta = await handler(ticket, args, cb) ?? undefined; - countSuccessResponse(methodName, 'success', meta?.source ?? 'handler'); + meta = await handler(ticket, args, cb) ?? undefined + countSuccessResponse(methodName, 'success', meta?.source ?? 'handler') } catch (err: any) { - failed = true; - failMsg = err?.message ?? 'Internal error'; + failed = true + failMsg = err?.message ?? 'Internal error' - countFailedResponse(methodName, failMsg); - cb({ code: err?.code ?? -32603, message: failMsg }, null); + countFailedResponse(methodName, failMsg) + cb({ code: err?.code ?? -32603, message: failMsg }, null) } finally { logEventEmitter.emit( 'fn_end', @@ -890,8 +895,9 @@ export function wrapApiMethod( export const methods = { + // Some example usage of WrapApiMethod() web3_clientVersion: wrapApiMethod( - 'web3_clientVersion', + 'web3_clientVersion ' + 'and getCurrentBlockInfo', async (_ticket, _args, cb) => { cb(null, 'Mist/v0.9.3/darwin/go1.4.1') } @@ -960,7 +966,7 @@ export const methods = { return { source: 'serviceValidator' }; } - // fallback RPC + // fallback try { const { result } = await getGasPrice(); cb(null, result); @@ -983,21 +989,27 @@ export const methods = { ), eth_blockNumber: wrapApiMethod( 'eth_blockNumber', + async (_t, _args, cb) => { - // collector + // collector const latest = await collectorAPI.getLatestBlockNumber(); if (latest) { cb(null, '0x' + latest.number.toString(16)); return { source: 'collector' }; } - // validator fallback + // validator fallback const { blockNumber, nodeUrl } = await getCurrentBlockInfo(); - const value = blockNumber ?? '0x0'; // keep original behaviour - cb(null, value); - return { nodeUrl, source: 'validator', note: blockNumber ? 'success' : 'null‑fallback' }; + + if (blockNumber == null) { + throw { message: 'blockNumber is null'}; + } + + cb(null, blockNumber); + return { nodeUrl, source: 'validator' }; } ), + eth_getBalance: wrapApiMethod( 'eth_getBalance', From 9b41faf59c3a8bcb343023d2d7bed99aaa02c228 Mon Sep 17 00:00:00 2001 From: Ungter Date: Fri, 18 Apr 2025 04:08:39 -0400 Subject: [PATCH 6/7] removed extra import --- package-lock.json | 2 +- package.json | 2 +- src/api.ts | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 654982f7..34ceddac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@shardeum-foundation/lib-crypto-utils": "4.3.0-prerelease.1", "@shardeum-foundation/lib-types": "1.4.0-prerelease.2", "axios": "1.6.1", - "better-sqlite3": "^9.0.0", + "better-sqlite3": "7.6.2", "body-parser": "1.19.0", "connect": "3.7.0", "cookie-parser": "1.4.6", diff --git a/package.json b/package.json index 3f61f07a..20f610dd 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@ethersproject/bignumber": "5.7.0", "@hapi/sntp": "4.0.0", "axios": "1.6.1", - "better-sqlite3": "^9.0.0", + "better-sqlite3": "7.6.2", "body-parser": "1.19.0", "connect": "3.7.0", "cookie-parser": "1.4.6", diff --git a/src/api.ts b/src/api.ts index d27eed09..fe97a296 100755 --- a/src/api.ts +++ b/src/api.ts @@ -53,7 +53,6 @@ import { archiverAPI } from './external/Archiver' import { TTLMap } from './utils/TTLMap' import { buildGetTransactionByBlockHashAndIndex } from './eth-handlers/eth_getTransactionByBlockHashAndIndex' import { buildGetTransactionByBlockNumberAndIndex } from './eth-handlers/eth_getTransactionByBlockNumberAndIndex' -import { performance } from 'perf_hooks' export const verbose = config.verbose export const firstLineLogs = config.firstLineLogs From 1542d3d2c06f3d8813c7db049b7c34ca66c25339 Mon Sep 17 00:00:00 2001 From: Ungter Date: Fri, 18 Apr 2025 04:11:53 -0400 Subject: [PATCH 7/7] ver fix --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 34ceddac..bb4d65ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3021,13 +3021,13 @@ } }, "node_modules/better-sqlite3": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz", - "integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.6.2.tgz", + "integrity": "sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg==", "hasInstallScript": true, "dependencies": { "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" + "prebuild-install": "^7.1.0" } }, "node_modules/binary-extensions": {