diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 3a4d1bd01d..50ba2589e9 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -7,10 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed - Fallback to singular provider if batch provider is not supported (#144) - -### Fixed - Fix missing ds option for event in dictionary query entries (#145) +### Added +- Custom provider for Celo (#147) + ## [2.11.1] - 2023-08-14 ### Changed - Synced with main sdk: diff --git a/packages/node/src/ethereum/api.ethereum.ts b/packages/node/src/ethereum/api.ethereum.ts index e4ae073506..73f9973c14 100644 --- a/packages/node/src/ethereum/api.ethereum.ts +++ b/packages/node/src/ethereum/api.ethereum.ts @@ -27,6 +27,9 @@ import { hexDataSlice, hexValue } from 'ethers/lib/utils'; import { retryOnFailEth } from '../utils/project'; import { yargsOptions } from '../yargs'; import { EthereumBlockWrapped } from './block.ethereum'; +import { CeloJsonRpcBatchProvider } from './ethers/celo/celo-json-rpc-batch-provider'; +import { CeloJsonRpcProvider } from './ethers/celo/celo-json-rpc-provider'; +import { CeloWsProvider } from './ethers/celo/celo-ws-provider'; import { JsonRpcBatchProvider } from './ethers/json-rpc-batch-provider'; import { JsonRpcProvider } from './ethers/json-rpc-provider'; import { ConnectionInfo } from './ethers/web'; @@ -130,11 +133,22 @@ export class EthereumApi implements ApiWrapper { async init(): Promise { this.injectClient(); + const network = await this.client.getNetwork(); + + //celo + if (network.chainId === 42220) { + if (this.client instanceof WebSocketProvider) { + this.client = new CeloWsProvider(this.client.connection.url); + } else { + this.client = new CeloJsonRpcBatchProvider(this.client.connection); + this.nonBatchClient = new CeloJsonRpcProvider(this.client.connection); + } + } + try { - const [genesisBlock, network, supportsFinalization, supportsSafe] = + const [genesisBlock, supportsFinalization, supportsSafe] = await Promise.all([ this.client.getBlock('earliest'), - this.client.getNetwork(), this.getSupportsTag('finalized'), this.getSupportsTag('safe'), ]); diff --git a/packages/node/src/ethereum/ethers/celo/celo-json-rpc-batch-provider.spec.ts b/packages/node/src/ethereum/ethers/celo/celo-json-rpc-batch-provider.spec.ts new file mode 100644 index 0000000000..b297abb1f6 --- /dev/null +++ b/packages/node/src/ethereum/ethers/celo/celo-json-rpc-batch-provider.spec.ts @@ -0,0 +1,36 @@ +// Copyright 2020-2023 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: GPL-3.0 + +import { BigNumber, constants, utils } from 'ethers'; +import { formatBlock } from '../../utils.ethereum'; +import { CeloJsonRpcBatchProvider } from './celo-json-rpc-batch-provider'; + +describe('CeloJsonRpcProvider', () => { + let provider: CeloJsonRpcBatchProvider; + + beforeEach(() => { + provider = new CeloJsonRpcBatchProvider('https://forno.celo.org'); + }); + + // Test if gasLimit is correctly set for blocks before the hard fork + it('should set gasLimit to zero for blocks before the hard fork', async () => { + const block = formatBlock( + await provider.send('eth_getBlockByNumber', [ + utils.hexValue(16068684), + true, + ]), + ); + expect(BigNumber.from(block.gasLimit)).toEqual(constants.Zero); + }); + + // Test if gasLimit is correctly set for blocks after the hard fork + it('should not set gasLimit to zero for blocks after the hard fork', async () => { + const block = formatBlock( + await provider.send('eth_getBlockByNumber', [ + utils.hexValue(21055596), + true, + ]), + ); + expect(BigNumber.from(block.gasLimit)).toEqual(BigNumber.from(0x1312d00)); + }); +}); diff --git a/packages/node/src/ethereum/ethers/celo/celo-json-rpc-batch-provider.ts b/packages/node/src/ethereum/ethers/celo/celo-json-rpc-batch-provider.ts new file mode 100644 index 0000000000..2b8c7deace --- /dev/null +++ b/packages/node/src/ethereum/ethers/celo/celo-json-rpc-batch-provider.ts @@ -0,0 +1,31 @@ +// Copyright 2020-2023 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: GPL-3.0 + +import { Networkish } from '@ethersproject/networks'; +import { getLogger } from '@subql/node-core'; +import { BigNumber, constants } from 'ethers'; +import { JsonRpcBatchProvider } from '../json-rpc-batch-provider'; +import { ConnectionInfo } from '../web'; + +const logger = getLogger('celo-batch-provider'); + +export class CeloJsonRpcBatchProvider extends JsonRpcBatchProvider { + private flanHardForkBlock = BigNumber.from('16068685'); + constructor(url?: ConnectionInfo | string, network?: Networkish) { + super(url, network); + + const originalBlockFormatter = this.formatter._block; + this.formatter._block = (value, format) => { + return originalBlockFormatter( + { + gasLimit: + BigNumber.from(value.number) < this.flanHardForkBlock + ? constants.Zero + : value.gasLimit, + ...value, + }, + format, + ); + }; + } +} diff --git a/packages/node/src/ethereum/ethers/celo/celo-json-rpc-provider.spec.ts b/packages/node/src/ethereum/ethers/celo/celo-json-rpc-provider.spec.ts new file mode 100644 index 0000000000..304d6b6413 --- /dev/null +++ b/packages/node/src/ethereum/ethers/celo/celo-json-rpc-provider.spec.ts @@ -0,0 +1,36 @@ +// Copyright 2020-2023 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: GPL-3.0 + +import { BigNumber, constants, utils } from 'ethers'; +import { formatBlock } from '../../utils.ethereum'; +import { CeloJsonRpcProvider } from './celo-json-rpc-provider'; + +describe('CeloJsonRpcProvider', () => { + let provider: CeloJsonRpcProvider; + + beforeEach(() => { + provider = new CeloJsonRpcProvider('https://forno.celo.org'); + }); + + // Test if gasLimit is correctly set for blocks before the hard fork + it('should set gasLimit to zero for blocks before the hard fork', async () => { + const block = formatBlock( + await provider.send('eth_getBlockByNumber', [ + utils.hexValue(16068684), + true, + ]), + ); + expect(BigNumber.from(block.gasLimit)).toEqual(constants.Zero); + }); + + // Test if gasLimit is correctly set for blocks after the hard fork + it('should not set gasLimit to zero for blocks after the hard fork', async () => { + const block = formatBlock( + await provider.send('eth_getBlockByNumber', [ + utils.hexValue(21055596), + true, + ]), + ); + expect(BigNumber.from(block.gasLimit)).toEqual(BigNumber.from(0x1312d00)); + }); +}); diff --git a/packages/node/src/ethereum/ethers/celo/celo-json-rpc-provider.ts b/packages/node/src/ethereum/ethers/celo/celo-json-rpc-provider.ts new file mode 100644 index 0000000000..bf086bd5b6 --- /dev/null +++ b/packages/node/src/ethereum/ethers/celo/celo-json-rpc-provider.ts @@ -0,0 +1,31 @@ +// Copyright 2020-2023 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: GPL-3.0 + +import { Networkish } from '@ethersproject/networks'; +import { getLogger } from '@subql/node-core'; +import { BigNumber, constants } from 'ethers'; +import { JsonRpcProvider } from '../json-rpc-provider'; +import { ConnectionInfo } from '../web'; + +const logger = getLogger('celo-provider'); + +export class CeloJsonRpcProvider extends JsonRpcProvider { + private flanHardForkBlock = BigNumber.from('16068685'); + constructor(url?: ConnectionInfo | string, network?: Networkish) { + super(url, network); + + const originalBlockFormatter = this.formatter._block; + this.formatter._block = (value, format) => { + return originalBlockFormatter( + { + gasLimit: + BigNumber.from(value.number) < this.flanHardForkBlock + ? constants.Zero + : value.gasLimit, + ...value, + }, + format, + ); + }; + } +} diff --git a/packages/node/src/ethereum/ethers/celo/celo-ws-provider.spec.ts b/packages/node/src/ethereum/ethers/celo/celo-ws-provider.spec.ts new file mode 100644 index 0000000000..236e04e2c9 --- /dev/null +++ b/packages/node/src/ethereum/ethers/celo/celo-ws-provider.spec.ts @@ -0,0 +1,36 @@ +// Copyright 2020-2023 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: GPL-3.0 + +import { BigNumber, constants, utils } from 'ethers'; +import { formatBlock } from '../../utils.ethereum'; +import { CeloWsProvider } from './celo-ws-provider'; + +describe('CeloJsonRpcProvider', () => { + let provider: CeloWsProvider; + + beforeEach(() => { + provider = new CeloWsProvider('https://forno.celo.org'); + }); + + // Test if gasLimit is correctly set for blocks before the hard fork + it('should set gasLimit to zero for blocks before the hard fork', async () => { + const block = formatBlock( + await provider.send('eth_getBlockByNumber', [ + utils.hexValue(16068684), + true, + ]), + ); + expect(BigNumber.from(block.gasLimit)).toEqual(constants.Zero); + }); + + // Test if gasLimit is correctly set for blocks after the hard fork + it('should not set gasLimit to zero for blocks after the hard fork', async () => { + const block = formatBlock( + await provider.send('eth_getBlockByNumber', [ + utils.hexValue(21055596), + true, + ]), + ); + expect(BigNumber.from(block.gasLimit)).toEqual(BigNumber.from(0x1312d00)); + }); +}); diff --git a/packages/node/src/ethereum/ethers/celo/celo-ws-provider.ts b/packages/node/src/ethereum/ethers/celo/celo-ws-provider.ts new file mode 100644 index 0000000000..278897f068 --- /dev/null +++ b/packages/node/src/ethereum/ethers/celo/celo-ws-provider.ts @@ -0,0 +1,30 @@ +// Copyright 2020-2023 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: GPL-3.0 + +import { Networkish } from '@ethersproject/networks'; +import { WebSocketProvider } from '@ethersproject/providers'; +import { getLogger } from '@subql/node-core'; +import { BigNumber, constants } from 'ethers'; + +const logger = getLogger('celo-ws-provider'); + +export class CeloWsProvider extends WebSocketProvider { + private flanHardForkBlock = BigNumber.from('16068685'); + constructor(url?: string, network?: Networkish) { + super(url, network); + + const originalBlockFormatter = this.formatter._block; + this.formatter._block = (value, format) => { + return originalBlockFormatter( + { + gasLimit: + BigNumber.from(value.number) < this.flanHardForkBlock + ? constants.Zero + : value.gasLimit, + ...value, + }, + format, + ); + }; + } +} diff --git a/packages/node/src/ethereum/utils.ethereum.ts b/packages/node/src/ethereum/utils.ethereum.ts index 423dc79519..05792ee32d 100644 --- a/packages/node/src/ethereum/utils.ethereum.ts +++ b/packages/node/src/ethereum/utils.ethereum.ts @@ -33,6 +33,9 @@ function handleNumber(value: string | number): BigNumber { if (value === '0x') { return Zero; } + if (value === null) { + return Zero; + } return BigNumber.from(value); }