From b64dfbaebaf3d8de7e62cdc918c6b2672397fa00 Mon Sep 17 00:00:00 2001 From: giurgiur99 Date: Thu, 19 Mar 2026 15:57:13 +0200 Subject: [PATCH 1/3] timeout request --- src/components/Indexer/utils.ts | 5 ++--- src/utils/blockchain.ts | 12 +++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/Indexer/utils.ts b/src/components/Indexer/utils.ts index 84445dfed..33aa87958 100644 --- a/src/components/Indexer/utils.ts +++ b/src/components/Indexer/utils.ts @@ -61,10 +61,9 @@ export const getDeployedContractBlock = (network: number) => { return deployedBlock } -export const getNetworkHeight = async (provider: FallbackProvider) => { - const networkHeight = await provider.getBlockNumber() - return networkHeight +export const getNetworkHeight = async (provider: FallbackProvider): Promise => { + return provider.getBlockNumber() } export const retrieveChunkEvents = async ( diff --git a/src/utils/blockchain.ts b/src/utils/blockchain.ts index 2c80161c8..f7b23b460 100644 --- a/src/utils/blockchain.ts +++ b/src/utils/blockchain.ts @@ -6,6 +6,7 @@ import { JsonRpcApiProvider, JsonRpcProvider, FallbackProvider, + FetchRequest, isAddress, parseUnits, Wallet, @@ -19,6 +20,8 @@ import { ValidateChainId } from '../@types/commands.js' import { OceanNodeConfig } from '../@types/OceanNode.js' import { KeyManager } from '../components/KeyManager/index.js' +const RPC_REQUEST_TIMEOUT_MS = 5_000 + export class Blockchain { private config?: OceanNodeConfig // Optional for new constructor private static signers: Map = new Map() @@ -66,20 +69,23 @@ export class Blockchain { public async getProvider(force: boolean = false): Promise { if (!this.provider) { for (const rpc of this.knownRPCs) { - const rpcProvider = new JsonRpcProvider(rpc) + const fetchReq = new FetchRequest(rpc) + fetchReq.timeout = RPC_REQUEST_TIMEOUT_MS + const rpcProvider = new JsonRpcProvider(fetchReq) // filter wrong chains or broken RPCs if (!force) { try { const { chainId } = await rpcProvider.getNetwork() if (chainId.toString() === this.chainId.toString()) { this.providers.push(rpcProvider) - break + // do not break — add all valid RPCs so FallbackProvider has real alternatives + // when one stalls, otherwise #waitForQuorum hangs with no runner to rescue it } } catch (error) { CORE_LOGGER.error(`Error getting network for RPC ${rpc}: ${error}`) } } else { - this.providers.push(new JsonRpcProvider(rpc)) + this.providers.push(new JsonRpcProvider(fetchReq)) } } this.provider = new FallbackProvider(this.providers) From 2042b3646e73a43e1b9ff14a0e163aed5f8287b3 Mon Sep 17 00:00:00 2001 From: giurgiur99 Date: Thu, 19 Mar 2026 17:10:22 +0200 Subject: [PATCH 2/3] recreate provider --- src/components/Indexer/ChainIndexer.ts | 10 ++++++++-- src/utils/blockchain.ts | 12 ++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/Indexer/ChainIndexer.ts b/src/components/Indexer/ChainIndexer.ts index 66da722cb..8176794ac 100644 --- a/src/components/Indexer/ChainIndexer.ts +++ b/src/components/Indexer/ChainIndexer.ts @@ -163,8 +163,8 @@ export class ChainIndexer { `Initial details for chain ${this.blockchain.getSupportedChain()}: RPCS start block: ${this.rpcDetails.startBlock}, Contract deployment block: ${contractDeploymentBlock}, Crawling start block: ${crawlingStartBlock}` ) - const provider = await this.blockchain.getProvider() - const signer = await this.blockchain.getSigner() + let provider = await this.blockchain.getProvider() + let signer = await this.blockchain.getSigner() const interval = getCrawlingInterval() let chunkSize = this.rpcDetails.chunkSize || 1 let successfulRetrievalCount = 0 @@ -298,7 +298,13 @@ export class ChainIndexer { INDEXER_LOGGER.error( `Error in indexing loop for chain ${this.blockchain.getSupportedChain()}: ${error.message}` ) + // Reset the provider so ethers recreates it fresh on next iteration. + // FallbackProvider permanently marks configs as _lastFatalError after + // any RPC failure — without reset, all subsequent calls throw immediately. + this.blockchain.resetProvider() await sleep(interval) + provider = await this.blockchain.getProvider() + signer = await this.blockchain.getSigner() } finally { lockProcessing = false } diff --git a/src/utils/blockchain.ts b/src/utils/blockchain.ts index f7b23b460..d1f0099eb 100644 --- a/src/utils/blockchain.ts +++ b/src/utils/blockchain.ts @@ -111,6 +111,18 @@ export class Blockchain { return this.knownRPCs } + /** + * Reset the cached provider and signer so they are recreated on the next call. + * Needed because ethers.js permanently marks a FallbackProvider's config as + * _lastFatalError after a single RPC failure, making it return "no runners?!" + * on every subsequent call. + */ + public resetProvider(): void { + this.provider = undefined as undefined as FallbackProvider + this.providers = [] + this.signer = undefined as unknown as Signer + } + public async calculateGasCost(to: string, amount: bigint): Promise { const provider = await this.getProvider() const estimatedGas = await provider.estimateGas({ From b1c27f1965c7bb2f17f6980b89ed33518170cefa Mon Sep 17 00:00:00 2001 From: giurgiur99 Date: Thu, 19 Mar 2026 17:18:48 +0200 Subject: [PATCH 3/3] use rpcprovider instead of fallback --- src/components/Indexer/ChainIndexer.ts | 6 +- src/components/Indexer/index.ts | 2 +- src/components/Indexer/processor.ts | 6 +- .../Indexer/processors/BaseProcessor.ts | 6 +- .../DispenserActivatedEventProcessor.ts | 4 +- .../DispenserCreatedEventProcessor.ts | 4 +- .../DispenserDeactivatedEventProcessor.ts | 4 +- .../ExchangeActivatedEventProcessor.ts | 4 +- .../ExchangeCreatedEventProcessor.ts | 4 +- .../ExchangeDeactivatedEventProcessor.ts | 4 +- .../ExchangeRateChangedEventProcessor.ts | 4 +- .../processors/MetadataEventProcessor.ts | 4 +- .../processors/MetadataStateEventProcessor.ts | 4 +- .../processors/OrderReusedEventProcessor.ts | 4 +- .../processors/OrderStartedEventProcessor.ts | 4 +- src/components/Indexer/utils.ts | 7 +-- src/components/KeyManager/index.ts | 4 +- src/components/core/utils/feesHandler.ts | 4 +- src/components/core/utils/validateOrders.ts | 6 +- .../integration/transactionValidation.test.ts | 12 ++-- src/test/unit/blockchain.test.ts | 4 +- src/test/unit/credentials.test.ts | 2 +- src/utils/blockchain.ts | 63 ++++--------------- 23 files changed, 63 insertions(+), 103 deletions(-) diff --git a/src/components/Indexer/ChainIndexer.ts b/src/components/Indexer/ChainIndexer.ts index 8176794ac..640338e4b 100644 --- a/src/components/Indexer/ChainIndexer.ts +++ b/src/components/Indexer/ChainIndexer.ts @@ -1,5 +1,5 @@ import EventEmitter from 'node:events' -import { FallbackProvider, Log, Signer } from 'ethers' +import { JsonRpcProvider, Log, Signer } from 'ethers' import { SupportedNetwork } from '../../@types/blockchain.js' import { LOG_LEVELS_STR } from '../../utils/logging/Logger.js' import { isDefined, sleep } from '../../utils/util.js' @@ -299,7 +299,7 @@ export class ChainIndexer { `Error in indexing loop for chain ${this.blockchain.getSupportedChain()}: ${error.message}` ) // Reset the provider so ethers recreates it fresh on next iteration. - // FallbackProvider permanently marks configs as _lastFatalError after + // JsonRpcProvider permanently marks configs as _lastFatalError after // any RPC failure — without reset, all subsequent calls throw immediately. this.blockchain.resetProvider() await sleep(interval) @@ -441,7 +441,7 @@ export class ChainIndexer { * Uses FIFO (First-In, First-Out) order via shift() */ private async processReindexQueue( - provider: FallbackProvider, + provider: JsonRpcProvider, signer: Signer ): Promise { while (this.reindexQueue.length > 0) { diff --git a/src/components/Indexer/index.ts b/src/components/Indexer/index.ts index fd9e94476..57aca958c 100644 --- a/src/components/Indexer/index.ts +++ b/src/components/Indexer/index.ts @@ -218,7 +218,7 @@ export class OceanIndexer { interval: number = 5000 // in milliseconds, default 2 secs ): Promise { try { - const retryInterval = Math.max(blockchain.getKnownRPCs().length * 3000, interval) // give 2 secs per each one + const retryInterval = Math.max(3000, interval) const result = await this.startCrawler(blockchain) const dbActive = this.getDatabase() if (!dbActive || !(await isReachableConnection(dbActive.getConfig().url))) { diff --git a/src/components/Indexer/processor.ts b/src/components/Indexer/processor.ts index 4b8f8be88..99a5ef858 100644 --- a/src/components/Indexer/processor.ts +++ b/src/components/Indexer/processor.ts @@ -1,4 +1,4 @@ -import { ethers, Signer, FallbackProvider, Interface, getAddress } from 'ethers' +import { ethers, Signer, JsonRpcProvider, Interface, getAddress } from 'ethers' import { BlocksEvents, ProcessingEvents } from '../../@types/blockchain.js' import { EVENTS } from '../../utils/constants.js' import { getConfiguration } from '../../utils/config.js' @@ -58,7 +58,7 @@ function getEventProcessor(eventType: string, chainId: number): BaseEventProcess export const processChunkLogs = async ( logs: readonly ethers.Log[], signer: Signer, - provider: FallbackProvider, + provider: JsonRpcProvider, chainId: number ): Promise => { const storeEvents: BlocksEvents = {} @@ -185,7 +185,7 @@ export const processChunkLogs = async ( export const processBlocks = async ( blockLogs: ethers.Log[], signer: Signer, - provider: FallbackProvider, + provider: JsonRpcProvider, network: number, lastIndexedBlock: number, count: number diff --git a/src/components/Indexer/processors/BaseProcessor.ts b/src/components/Indexer/processors/BaseProcessor.ts index 3b98b415d..5afca34cf 100644 --- a/src/components/Indexer/processors/BaseProcessor.ts +++ b/src/components/Indexer/processors/BaseProcessor.ts @@ -9,7 +9,7 @@ import { hexlify, getBytes, toUtf8String, - FallbackProvider + JsonRpcProvider } from 'ethers' import { Readable } from 'winston-transport' import { DecryptDDOCommand, NonceCommand } from '../../../@types/commands.js' @@ -83,7 +83,7 @@ export abstract class BaseEventProcessor { } protected async getEventData( - provider: FallbackProvider, + provider: JsonRpcProvider, transactionHash: string, abi: any, eventType: string @@ -487,7 +487,7 @@ export abstract class BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider, + provider: JsonRpcProvider, eventName?: string ): Promise } diff --git a/src/components/Indexer/processors/DispenserActivatedEventProcessor.ts b/src/components/Indexer/processors/DispenserActivatedEventProcessor.ts index 501c0d91a..8538e2d57 100644 --- a/src/components/Indexer/processors/DispenserActivatedEventProcessor.ts +++ b/src/components/Indexer/processors/DispenserActivatedEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider, ZeroAddress } from 'ethers' +import { ethers, Signer, JsonRpcProvider, ZeroAddress } from 'ethers' import { EVENTS } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -20,7 +20,7 @@ export class DispenserActivatedEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { const decodedEventData = await this.getEventData( provider, diff --git a/src/components/Indexer/processors/DispenserCreatedEventProcessor.ts b/src/components/Indexer/processors/DispenserCreatedEventProcessor.ts index 84a517bfc..4997f9b41 100644 --- a/src/components/Indexer/processors/DispenserCreatedEventProcessor.ts +++ b/src/components/Indexer/processors/DispenserCreatedEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager, PriceType } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider, ZeroAddress } from 'ethers' +import { ethers, Signer, JsonRpcProvider, ZeroAddress } from 'ethers' import { EVENTS } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -20,7 +20,7 @@ export class DispenserCreatedEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { const decodedEventData = await this.getEventData( provider, diff --git a/src/components/Indexer/processors/DispenserDeactivatedEventProcessor.ts b/src/components/Indexer/processors/DispenserDeactivatedEventProcessor.ts index c89ef2ed8..bdb970932 100644 --- a/src/components/Indexer/processors/DispenserDeactivatedEventProcessor.ts +++ b/src/components/Indexer/processors/DispenserDeactivatedEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider, ZeroAddress } from 'ethers' +import { ethers, Signer, JsonRpcProvider, ZeroAddress } from 'ethers' import { EVENTS } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -20,7 +20,7 @@ export class DispenserDeactivatedEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { const decodedEventData = await this.getEventData( provider, diff --git a/src/components/Indexer/processors/ExchangeActivatedEventProcessor.ts b/src/components/Indexer/processors/ExchangeActivatedEventProcessor.ts index a99ce2f15..97d7dc2c4 100644 --- a/src/components/Indexer/processors/ExchangeActivatedEventProcessor.ts +++ b/src/components/Indexer/processors/ExchangeActivatedEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider, ZeroAddress } from 'ethers' +import { ethers, Signer, JsonRpcProvider, ZeroAddress } from 'ethers' import { EVENTS } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -20,7 +20,7 @@ export class ExchangeActivatedEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { try { if (!(await isValidFreContract(event.address, chainId, signer))) { diff --git a/src/components/Indexer/processors/ExchangeCreatedEventProcessor.ts b/src/components/Indexer/processors/ExchangeCreatedEventProcessor.ts index d13c0109d..b29f080b5 100644 --- a/src/components/Indexer/processors/ExchangeCreatedEventProcessor.ts +++ b/src/components/Indexer/processors/ExchangeCreatedEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider, ZeroAddress } from 'ethers' +import { ethers, Signer, JsonRpcProvider, ZeroAddress } from 'ethers' import { EVENTS } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -20,7 +20,7 @@ export class ExchangeCreatedEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { try { if (!(await isValidFreContract(event.address, chainId, signer))) { diff --git a/src/components/Indexer/processors/ExchangeDeactivatedEventProcessor.ts b/src/components/Indexer/processors/ExchangeDeactivatedEventProcessor.ts index d5f02c15c..3be19d233 100644 --- a/src/components/Indexer/processors/ExchangeDeactivatedEventProcessor.ts +++ b/src/components/Indexer/processors/ExchangeDeactivatedEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider, ZeroAddress } from 'ethers' +import { ethers, Signer, JsonRpcProvider, ZeroAddress } from 'ethers' import { EVENTS } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -20,7 +20,7 @@ export class ExchangeDeactivatedEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { if (!(await isValidFreContract(event.address, chainId, signer))) { INDEXER_LOGGER.error( diff --git a/src/components/Indexer/processors/ExchangeRateChangedEventProcessor.ts b/src/components/Indexer/processors/ExchangeRateChangedEventProcessor.ts index 52e2b0ef8..d47e3c962 100644 --- a/src/components/Indexer/processors/ExchangeRateChangedEventProcessor.ts +++ b/src/components/Indexer/processors/ExchangeRateChangedEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider, ZeroAddress } from 'ethers' +import { ethers, Signer, JsonRpcProvider, ZeroAddress } from 'ethers' import { EVENTS } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -20,7 +20,7 @@ export class ExchangeRateChangedEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { try { if (!(await isValidFreContract(event.address, chainId, signer))) { diff --git a/src/components/Indexer/processors/MetadataEventProcessor.ts b/src/components/Indexer/processors/MetadataEventProcessor.ts index 84a9e971c..c51898917 100644 --- a/src/components/Indexer/processors/MetadataEventProcessor.ts +++ b/src/components/Indexer/processors/MetadataEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager, DDO, VersionedDDO } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider, getAddress } from 'ethers' +import { ethers, Signer, JsonRpcProvider, getAddress } from 'ethers' import { ENVIRONMENT_VARIABLES, EVENTS, @@ -23,7 +23,7 @@ export class MetadataEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider, + provider: JsonRpcProvider, eventName: string ): Promise { let did = 'did:op' diff --git a/src/components/Indexer/processors/MetadataStateEventProcessor.ts b/src/components/Indexer/processors/MetadataStateEventProcessor.ts index 38a2e022c..f00828787 100644 --- a/src/components/Indexer/processors/MetadataStateEventProcessor.ts +++ b/src/components/Indexer/processors/MetadataStateEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider } from 'ethers' +import { ethers, Signer, JsonRpcProvider } from 'ethers' import { EVENTS, MetadataStates } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -13,7 +13,7 @@ export class MetadataStateEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, _signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { INDEXER_LOGGER.logMessage(`Processing metadata state event...`, true) const decodedEventData = await this.getEventData( diff --git a/src/components/Indexer/processors/OrderReusedEventProcessor.ts b/src/components/Indexer/processors/OrderReusedEventProcessor.ts index 0ffb5726d..3ea10d823 100644 --- a/src/components/Indexer/processors/OrderReusedEventProcessor.ts +++ b/src/components/Indexer/processors/OrderReusedEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider } from 'ethers' +import { ethers, Signer, JsonRpcProvider } from 'ethers' import { EVENTS } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -18,7 +18,7 @@ export class OrderReusedEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { const decodedEventData = await this.getEventData( provider, diff --git a/src/components/Indexer/processors/OrderStartedEventProcessor.ts b/src/components/Indexer/processors/OrderStartedEventProcessor.ts index 224f0cb7b..c2c6ad58a 100644 --- a/src/components/Indexer/processors/OrderStartedEventProcessor.ts +++ b/src/components/Indexer/processors/OrderStartedEventProcessor.ts @@ -1,5 +1,5 @@ import { DDOManager } from '@oceanprotocol/ddo-js' -import { ethers, Signer, FallbackProvider } from 'ethers' +import { ethers, Signer, JsonRpcProvider } from 'ethers' import { EVENTS } from '../../../utils/constants.js' import { getDatabase } from '../../../utils/database.js' import { INDEXER_LOGGER } from '../../../utils/logging/common.js' @@ -13,7 +13,7 @@ export class OrderStartedEventProcessor extends BaseEventProcessor { event: ethers.Log, chainId: number, signer: Signer, - provider: FallbackProvider + provider: JsonRpcProvider ): Promise { const decodedEventData = await this.getEventData( provider, diff --git a/src/components/Indexer/utils.ts b/src/components/Indexer/utils.ts index 33aa87958..1c7cbf29d 100644 --- a/src/components/Indexer/utils.ts +++ b/src/components/Indexer/utils.ts @@ -1,4 +1,4 @@ -import { Signer, ethers, getAddress, FallbackProvider } from 'ethers' +import { Signer, ethers, getAddress, JsonRpcProvider } from 'ethers' import ERC721Factory from '@oceanprotocol/contracts/artifacts/contracts/ERC721Factory.sol/ERC721Factory.json' with { type: 'json' } import ERC721Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC721Template.sol/ERC721Template.json' with { type: 'json' } import { EVENT_HASHES, isDefined } from '../../utils/index.js' @@ -61,14 +61,13 @@ export const getDeployedContractBlock = (network: number) => { return deployedBlock } - -export const getNetworkHeight = async (provider: FallbackProvider): Promise => { +export const getNetworkHeight = async (provider: JsonRpcProvider): Promise => { return provider.getBlockNumber() } export const retrieveChunkEvents = async ( signer: Signer, - provider: FallbackProvider, + provider: JsonRpcProvider, network: number, lastIndexedBlock: number, count: number diff --git a/src/components/KeyManager/index.ts b/src/components/KeyManager/index.ts index 407dffaf1..cc975c511 100644 --- a/src/components/KeyManager/index.ts +++ b/src/components/KeyManager/index.ts @@ -1,5 +1,5 @@ import type { PeerId } from '@libp2p/interface' -import { Signer, Wallet, FallbackProvider } from 'ethers' +import { Signer, Wallet, JsonRpcProvider } from 'ethers' import { IKeyProvider } from '../../@types/KeyManager.js' import { OceanNodeConfig } from '../../@types/OceanNode.js' import { RawPrivateKeyProvider } from './providers/RawPrivateKeyProvider.js' @@ -96,7 +96,7 @@ export class KeyManager { * @param provider - The JSON-RPC provider to connect the signer to * @returns An ethers Signer instance */ - async getEvmSigner(provider: FallbackProvider, chainId?: number): Promise { + async getEvmSigner(provider: JsonRpcProvider, chainId?: number): Promise { // Create a cache key based on chainId and provider URL // TO DO if (!chainId) { diff --git a/src/components/core/utils/feesHandler.ts b/src/components/core/utils/feesHandler.ts index b351e5cf8..d29481a13 100644 --- a/src/components/core/utils/feesHandler.ts +++ b/src/components/core/utils/feesHandler.ts @@ -1,6 +1,6 @@ import type { ComputeResourcesPricingInfo } from '../../../@types/C2D/C2D.js' import { - FallbackProvider, + JsonRpcProvider, ethers, Interface, BigNumberish, @@ -134,7 +134,7 @@ export async function createProviderFee( export async function verifyProviderFees( txId: string, userAddress: string, - provider: FallbackProvider, + provider: JsonRpcProvider, service: Service ): Promise { /* given a transaction, check if there is a valid provider fee event diff --git a/src/components/core/utils/validateOrders.ts b/src/components/core/utils/validateOrders.ts index 8210d150e..eb79cd7f3 100644 --- a/src/components/core/utils/validateOrders.ts +++ b/src/components/core/utils/validateOrders.ts @@ -1,4 +1,4 @@ -import { Contract, Interface, TransactionReceipt, Signer, FallbackProvider } from 'ethers' +import { Contract, Interface, TransactionReceipt, Signer, JsonRpcProvider } from 'ethers' import { fetchEventFromTransaction } from '../../../utils/util.js' import ERC20Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20TemplateEnterprise.sol/ERC20TemplateEnterprise.json' with { type: 'json' } import ERC721Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC721Template.sol/ERC721Template.json' with { type: 'json' } @@ -14,7 +14,7 @@ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) export async function fetchTransactionReceipt( txId: string, - provider: FallbackProvider, + provider: JsonRpcProvider, retries: number = 2 ): Promise { while (retries > 0) { @@ -39,7 +39,7 @@ export async function fetchTransactionReceipt( export async function validateOrderTransaction( txId: string, userAddress: string, - provider: FallbackProvider, + provider: JsonRpcProvider, dataNftAddress: string, datatokenAddress: string, serviceIndex: number, diff --git a/src/test/integration/transactionValidation.test.ts b/src/test/integration/transactionValidation.test.ts index 651be6af9..08dc2d0f9 100644 --- a/src/test/integration/transactionValidation.test.ts +++ b/src/test/integration/transactionValidation.test.ts @@ -1,5 +1,5 @@ import { expect, assert } from 'chai' -import { JsonRpcProvider, Signer, FallbackProvider } from 'ethers' +import { JsonRpcProvider, Signer } from 'ethers' import { validateOrderTransaction } from '../../components/core/utils/validateOrders.js' import { expectedTimeoutFailure, waitToIndex } from './testUtils.js' import { genericDDO } from '../data/ddo.js' @@ -29,7 +29,6 @@ describe('validateOrderTransaction Function with Orders', () => { let database: Database let oceanNode: OceanNode let provider: JsonRpcProvider - let fallbackProvider: FallbackProvider let publisherAccount: Signer let consumerAccount: Signer let consumerAddress: string @@ -83,7 +82,6 @@ describe('validateOrderTransaction Function with Orders', () => { } provider = new JsonRpcProvider('http://127.0.0.1:8545') - fallbackProvider = new FallbackProvider([provider]) publisherAccount = (await provider.getSigner(0)) as Signer consumerAccount = (await provider.getSigner(1)) as Signer @@ -155,7 +153,7 @@ describe('validateOrderTransaction Function with Orders', () => { const validationResult = await validateOrderTransaction( orderTxId, consumerAddress, - fallbackProvider, + provider, dataNftAddress, datatokenAddress, parseInt(serviceId), @@ -185,7 +183,7 @@ describe('validateOrderTransaction Function with Orders', () => { const validationResult = await validateOrderTransaction( reOrderTxId, consumerAddress, - fallbackProvider, + provider, dataNftAddress, datatokenAddress, parseInt(serviceId), @@ -204,7 +202,7 @@ describe('validateOrderTransaction Function with Orders', () => { const validationResult = await validateOrderTransaction( reOrderTxId, consumerAddress, - fallbackProvider, + provider, dataNftAddress, datatokenAddress, parseInt('999'), @@ -223,7 +221,7 @@ describe('validateOrderTransaction Function with Orders', () => { const validationResult = await validateOrderTransaction( reOrderTxId, '0x0', - fallbackProvider, + provider, dataNftAddress, datatokenAddress, parseInt(serviceId), diff --git a/src/test/unit/blockchain.test.ts b/src/test/unit/blockchain.test.ts index 737157c19..8850452f2 100644 --- a/src/test/unit/blockchain.test.ts +++ b/src/test/unit/blockchain.test.ts @@ -37,8 +37,8 @@ describe('Should validate blockchain network connections', () => { blockchain = blockchainRegistry.getBlockchain(8996) }) - it('should get known rpcs', () => { - expect(blockchain.getKnownRPCs().length).to.be.equal(3) + it('should get rpc', () => { + expect(blockchain.getRpc()).to.be.a('string') }) it('should get network not ready', async function () { diff --git a/src/test/unit/credentials.test.ts b/src/test/unit/credentials.test.ts index 3fa2e7664..9dcbc6bfc 100644 --- a/src/test/unit/credentials.test.ts +++ b/src/test/unit/credentials.test.ts @@ -38,7 +38,7 @@ describe('credentials', () => { const blockchains = new BlockchainRegistry(keyManager, config) blockchain = blockchains.getBlockchain(DEVELOPMENT_CHAIN_ID) // force usage of known bad provider - await blockchain.getProvider(true) + await blockchain.getProvider() signer = await blockchain.getSigner() }) diff --git a/src/utils/blockchain.ts b/src/utils/blockchain.ts index d1f0099eb..ca018d0d4 100644 --- a/src/utils/blockchain.ts +++ b/src/utils/blockchain.ts @@ -5,7 +5,6 @@ import { Contract, JsonRpcApiProvider, JsonRpcProvider, - FallbackProvider, FetchRequest, isAddress, parseUnits, @@ -28,16 +27,10 @@ export class Blockchain { private static providers: Map = new Map() private keyManager: KeyManager private signer: Signer - private provider: FallbackProvider - private providers: JsonRpcProvider[] = [] + private provider: JsonRpcProvider + private rpc: string private chainId: number - private knownRPCs: string[] = [] - /** - * Constructor overloads: - * 1. New pattern: (rpc, chainId, signer, fallbackRPCs?) - signer provided by KeyManager - * 2. Old pattern: (rpc, chainId, config, fallbackRPCs?) - for backward compatibility - */ public constructor( keyManager: KeyManager, rpc: string, @@ -46,11 +39,8 @@ export class Blockchain { ) { this.chainId = chainId this.keyManager = keyManager - this.knownRPCs.push(rpc) - if (fallbackRPCs && fallbackRPCs.length > 0) { - this.knownRPCs.push(...fallbackRPCs) - } - this.provider = undefined as undefined as FallbackProvider + this.rpc = rpc + this.provider = undefined as unknown as JsonRpcProvider this.signer = undefined as unknown as Signer } @@ -66,39 +56,19 @@ export class Blockchain { return await this.signer.getAddress() } - public async getProvider(force: boolean = false): Promise { + public async getProvider(): Promise { if (!this.provider) { - for (const rpc of this.knownRPCs) { - const fetchReq = new FetchRequest(rpc) - fetchReq.timeout = RPC_REQUEST_TIMEOUT_MS - const rpcProvider = new JsonRpcProvider(fetchReq) - // filter wrong chains or broken RPCs - if (!force) { - try { - const { chainId } = await rpcProvider.getNetwork() - if (chainId.toString() === this.chainId.toString()) { - this.providers.push(rpcProvider) - // do not break — add all valid RPCs so FallbackProvider has real alternatives - // when one stalls, otherwise #waitForQuorum hangs with no runner to rescue it - } - } catch (error) { - CORE_LOGGER.error(`Error getting network for RPC ${rpc}: ${error}`) - } - } else { - this.providers.push(new JsonRpcProvider(fetchReq)) - } - } - this.provider = new FallbackProvider(this.providers) + const fetchReq = new FetchRequest(this.rpc) + fetchReq.timeout = RPC_REQUEST_TIMEOUT_MS + this.provider = new JsonRpcProvider(fetchReq) } return this.provider } public async getSigner(): Promise { if (!this.signer) { - if (!this.provider) { - await this.getProvider() - } - this.signer = await this.keyManager.getEvmSigner(this.provider, this.chainId) + const provider = await this.getProvider() + this.signer = await this.keyManager.getEvmSigner(provider, this.chainId) } return this.signer } @@ -107,19 +77,12 @@ export class Blockchain { return await this.detectNetwork() } - public getKnownRPCs(): string[] { - return this.knownRPCs + public getRpc(): string { + return this.rpc } - /** - * Reset the cached provider and signer so they are recreated on the next call. - * Needed because ethers.js permanently marks a FallbackProvider's config as - * _lastFatalError after a single RPC failure, making it return "no runners?!" - * on every subsequent call. - */ public resetProvider(): void { - this.provider = undefined as undefined as FallbackProvider - this.providers = [] + this.provider = undefined as unknown as JsonRpcProvider this.signer = undefined as unknown as Signer }