Skip to content

Commit

Permalink
Strict node (#325)
Browse files Browse the repository at this point in the history
* some chagne

* some change

* some change

* some chagne

* some change

* some change

* Update deps, fix build issues

* some changes

* Update the dependency version of @subql/types-core.

* Revert "Update the dependency version of @subql/types-core."

This reverts commit 1a9acb1.

* enable ts strict

* change log

* some change

---------

Co-authored-by: Scott Twiname <[email protected]>
  • Loading branch information
yoozo and stwiname authored Jul 19, 2024
1 parent 4f5331a commit 2b1f2b6
Show file tree
Hide file tree
Showing 42 changed files with 262 additions and 198 deletions.
8 changes: 8 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,12 @@ module.exports = {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
},
overrides: [
{
files: ['*.test.ts', '*.spec.ts'],
rules: {
'@typescript-eslint/no-non-null-assertion': 'off', // @typescript-eslint/no-non-null-assertion
},
},
],
};
3 changes: 3 additions & 0 deletions packages/common-ethereum/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Enable ts strict

## [4.3.0] - 2024-07-11
### Changed
- Bump with `@subql/common` (#2487)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ describe('Codegen spec', () => {
},
],
},
};
} as any;

await expect(
generateAbis([ds], PROJECT_PATH, undefined as any, undefined as any, undefined as any)
Expand Down
4 changes: 1 addition & 3 deletions packages/common-ethereum/src/project/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
SubqlRuntimeHandler,
SubqlRuntimeDatasource,
SubqlCustomDatasource,
CustomDataSourceAsset,
EthereumBlockFilter,
SubqlBlockHandler,
SubqlEventHandler,
Expand All @@ -29,7 +28,6 @@ import {
IsString,
IsObject,
ValidateNested,
IsEthereumAddress,
registerDecorator,
ValidationOptions,
ValidationArguments,
Expand Down Expand Up @@ -212,7 +210,7 @@ export class CustomDataSourceBase<K extends string, M extends SubqlMapping = Sub
mapping!: M;
@Type(() => FileReferenceImpl)
@ValidateNested({each: true})
assets!: Map<string, CustomDataSourceAsset>;
assets!: Map<string, FileReference>;
@Type(() => FileReferenceImpl)
@IsObject()
processor!: FileReference;
Expand Down
6 changes: 5 additions & 1 deletion packages/common-ethereum/src/project/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
EthereumHandlerKind,
SubqlRuntimeDatasource,
SecondLayerHandlerProcessorArray,
SubqlCustomHandler,
SubqlMapping,
} from '@subql/types-ethereum';
import {fromBech32Address} from '@zilliqa-js/crypto';
import {buildMessage, isEthereumAddress, ValidateBy, ValidationOptions} from 'class-validator';
Expand Down Expand Up @@ -39,7 +41,9 @@ export function isCallHandlerProcessor<E>(
return hp.baseHandlerKind === EthereumHandlerKind.Call;
}

export function isCustomDs(ds: SubqlDatasource): ds is SubqlCustomDatasource<string> {
export function isCustomDs<F extends SubqlMapping<SubqlCustomHandler>>(
ds: SubqlDatasource
): ds is SubqlCustomDatasource<string, F> {
return ds.kind !== EthereumDatasourceKind.Runtime && !!(ds as SubqlCustomDatasource<string>).processor;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Enable ts strict mode

## [4.7.3] - 2024-07-16
### Fixed
- Fix dockerfile missing set timezone to UTC (#329)
Expand Down
2 changes: 1 addition & 1 deletion packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@nestjs/schedule": "^3.0.1",
"@subql/common-ethereum": "workspace:*",
"@subql/node-core": "^11.0.0",
"@subql/testing": "^2.1.1",
"@subql/testing": "^2.2.1",
"@subql/types-ethereum": "workspace:*",
"cacheable-lookup": "6",
"ethers": "^5.7.0",
Expand Down
8 changes: 3 additions & 5 deletions packages/node/src/configure/SubqueryProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ export type EthereumProjectDsTemplate =

export type SubqlProjectBlockFilter = EthereumBlockFilter & CronFilter;

const NOT_SUPPORT = (name: string) => {
throw new Error(`Manifest specVersion ${name} is not supported`);
};

// This is the runtime type after we have mapped genesisHash to chainId and endpoint/dict have been provided when dealing with deployments
type NetworkConfig = EthereumNetworkConfig & { chainId: string };

Expand Down Expand Up @@ -105,7 +101,9 @@ export class SubqueryProject implements ISubqueryProject {
networkOverrides,
);
} else {
NOT_SUPPORT(manifest.specVersion);
throw new Error(
`Manifest specVersion ${manifest.specVersion} is not supported`,
);
}
}
}
Expand Down
20 changes: 10 additions & 10 deletions packages/node/src/ethereum/api.ethereum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ describe('Api.ethereum', () => {
expect(typeof blockData.transactions[0].receipt).toEqual('function');
expect(typeof blockData.logs[0].transaction.receipt).toEqual('function');
expect(typeof blockData.logs[0].transaction.from).toEqual('string');
expect(typeof blockData.transactions[81].logs[0].transaction.from).toEqual(
expect(typeof blockData.transactions[81].logs![0].transaction.from).toEqual(
'string',
);
expect(
typeof blockData.transactions[81].logs[0].transaction.receipt,
typeof blockData.transactions[81].logs![0].transaction.receipt,
).toEqual('function');
});

Expand All @@ -89,8 +89,8 @@ describe('Api.ethereum', () => {
e.hash ===
'0x8e419d0e36d7f9c099a001fded516bd168edd9d27b4aec2bcd56ba3b3b955ccc',
);
const parsedTx = await ethApi.parseTransaction(tx, ds);
expect(parsedTx.logs[0].args).toBeTruthy();
const parsedTx = await ethApi.parseTransaction(tx!, ds);
expect(parsedTx.logs![0].args).toBeTruthy();
});

it('Should decode transaction data and not clone object', async () => {
Expand All @@ -99,7 +99,7 @@ describe('Api.ethereum', () => {
e.hash ===
'0x8e419d0e36d7f9c099a001fded516bd168edd9d27b4aec2bcd56ba3b3b955ccc',
);
const parsedTx = await ethApi.parseTransaction(tx, ds);
const parsedTx = await ethApi.parseTransaction(tx!, ds);

expect(parsedTx).toBe(tx);
});
Expand All @@ -111,7 +111,7 @@ describe('Api.ethereum', () => {
e.hash ===
'0xed62f7a7720fe6ae05dec45ad9dd4f53034a0aae2c140d229b1151504ee9a6c9',
);
const parsedLog = await ethApi.parseLog(tx.logs[0], ds);
const parsedLog = await ethApi.parseLog(tx!.logs![0], ds);
expect(parsedLog).not.toHaveProperty('args');
expect(parsedLog).toBeTruthy();
});
Expand All @@ -122,7 +122,7 @@ describe('Api.ethereum', () => {
(e) =>
e.hash ===
'0x8e419d0e36d7f9c099a001fded516bd168edd9d27b4aec2bcd56ba3b3b955ccc',
).logs[1];
)!.logs![1];

const parsedLog = await ethApi.parseLog(log, ds);
expect(parsedLog).toBe(log);
Expand Down Expand Up @@ -174,12 +174,12 @@ describe('Api.ethereum', () => {
(tx) =>
tx.hash ===
'0xeb2e443f2d4e784193fa13bbbae2b85e6ee459e7b7b53f8ca098ffae9b25b059',
);
)!;

const erc20Transfers = transaction.logs.filter((log) =>
const erc20Transfers = transaction.logs!.filter((log) =>
filterLogsProcessor(log, filter_2),
);
const erc721Transfers = transaction.logs.filter((log) =>
const erc721Transfers = transaction.logs!.filter((log) =>
filterLogsProcessor(log, filter_1),
);

Expand Down
41 changes: 27 additions & 14 deletions packages/node/src/ethereum/api.ethereum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import assert from 'assert';
import fs from 'fs';
import http from 'http';
import https from 'https';
Expand Down Expand Up @@ -96,10 +97,10 @@ export class EthereumApi implements ApiWrapper {

// This is used within the sandbox when HTTP is used
private nonBatchClient?: JsonRpcProvider;
private genesisBlock: Record<string, any>;
private _genesisBlock?: Record<string, any>;
private contractInterfaces: Record<string, Interface> = {};
private chainId: number;
private name: string;
private chainId?: number;
private name?: string;

// Ethereum POS
private _supportsFinalization = true;
Expand Down Expand Up @@ -147,6 +148,13 @@ export class EthereumApi implements ApiWrapper {
}
}

private get genesisBlock(): Record<string, any> {
if (!this._genesisBlock) {
throw new Error('Genesis block is not available');
}
return this._genesisBlock;
}

async init(): Promise<void> {
this.injectClient();

Expand All @@ -170,12 +178,15 @@ export class EthereumApi implements ApiWrapper {
this.getSupportsTag('safe'),
]);

this.genesisBlock = genesisBlock;
this._genesisBlock = genesisBlock;
this._supportsFinalization = supportsFinalization && supportsSafe;
this.chainId = network.chainId;
this.name = network.name;
} catch (e) {
if ((e as Error).message.startsWith('Invalid response')) {
if (!this.nonBatchClient) {
throw new Error('No suitable client found');
}
this.client = this.nonBatchClient;

logger.warn(
Expand Down Expand Up @@ -206,9 +217,9 @@ export class EthereumApi implements ApiWrapper {
private injectClient(): void {
const orig = this.client.send.bind(this.client);
Object.defineProperty(this.client, 'send', {
value: (...args) => {
value: (method: string, args: any[]) => {
this.eventEmitter.emit('rpcCall');
return orig(...args);
return orig(method, args);
},
});
}
Expand Down Expand Up @@ -261,10 +272,12 @@ export class EthereumApi implements ApiWrapper {
}

getRuntimeChain(): string {
assert(this.name, 'Api has not been initialised');
return this.name;
}

getChainId(): number {
assert(this.chainId, 'Api has not been initialised');
return this.chainId;
}

Expand Down Expand Up @@ -337,15 +350,13 @@ export class EthereumApi implements ApiWrapper {
block.transactions = block.transactions.map((tx) => ({
...formatTransaction(tx, block),
receipt: () =>
this.getTransactionReceipt(tx.hash).then((r) =>
formatReceipt(r, block),
),
this.getTransactionReceipt(tx.hash).then((r) => formatReceipt(r)),
logs: block.logs.filter((l) => l.transactionHash === tx.hash),
}));

this.eventEmitter.emit('fetchBlock');
return formatBlockUtil(block);
} catch (e) {
} catch (e: any) {
throw this.handleError(e);
}
}
Expand Down Expand Up @@ -386,13 +397,15 @@ export class EthereumApi implements ApiWrapper {
? this.client
: this.nonBatchClient;

assert(client, 'Unable to find client to make SafeApi');

return new SafeEthProvider(client, blockHeight);
}

private buildInterface(
abiName: string,
assets: Record<string, string>,
): Interface | undefined {
): Interface {
if (!assets[abiName]) {
throw new Error(`ABI named "${abiName}" not referenced in assets`);
}
Expand All @@ -412,7 +425,7 @@ export class EthereumApi implements ApiWrapper {
}

this.contractInterfaces[abiName] = new Interface(abiObj);
} catch (e) {
} catch (e: any) {
logger.error(`Unable to parse ABI: ${e.message}`);
throw new Error('ABI is invalid');
}
Expand All @@ -437,7 +450,7 @@ export class EthereumApi implements ApiWrapper {
log.args = iface?.parseLog(log).args as T;

return log;
} catch (e) {
} catch (e: any) {
logger.warn(`Failed to parse log data: ${e.message}`);
return log;
}
Expand Down Expand Up @@ -467,7 +480,7 @@ export class EthereumApi implements ApiWrapper {

transaction.args = args;
return transaction;
} catch (e) {
} catch (e: any) {
logger.warn(`Failed to parse transaction data: ${e.message}`);
return transaction;
}
Expand Down
24 changes: 18 additions & 6 deletions packages/node/src/ethereum/api.service.ethereum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import assert from 'assert';
import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
Expand Down Expand Up @@ -36,8 +37,11 @@ export class EthereumApiService extends ApiService<
SafeEthProvider,
IBlock<EthereumBlock>[] | IBlock<LightEthereumBlock>[]
> {
private fetchBlocksFunction: FetchFunc;
private fetchBlocksBatches: GetFetchFunc = () => this.fetchBlocksFunction;
private fetchBlocksFunction?: FetchFunc;
private fetchBlocksBatches: GetFetchFunc = () => {
assert(this.fetchBlocksFunction, 'Fetch blocks function is not defined');
return this.fetchBlocksFunction;
};
private nodeConfig: EthereumNodeConfig;

constructor(
Expand Down Expand Up @@ -114,14 +118,19 @@ export class EthereumApiService extends ApiService<
get: (target, prop, receiver) => {
const originalMethod = target[prop as keyof SafeEthProvider];
if (typeof originalMethod === 'function') {
return async (...args: any[]) => {
return async (
...args: Parameters<typeof originalMethod>
): Promise<ReturnType<typeof originalMethod>> => {
let retries = 0;
let currentApi = target;
let throwingError: Error;
let throwingError: Error | undefined;

while (retries < maxRetries) {
try {
return await originalMethod.apply(currentApi, args);
return await (originalMethod as Function).apply(
currentApi,
args,
);
} catch (error: any) {
// other than retryErrorCodes, other errors does not have anything to do with network request, retrying would not change its outcome
if (!retryErrorCodes.includes(error?.code)) {
Expand All @@ -140,6 +149,9 @@ export class EthereumApiService extends ApiService<
logger.error(
`Maximum retries (${maxRetries}) exceeded for api at height ${height}`,
);
if (!throwingError) {
throw new Error('Failed to make request, maximum retries failed');
}
throw throwingError;
};
}
Expand Down Expand Up @@ -198,7 +210,7 @@ export class EthereumApiService extends ApiService<
fetchFunc,
'SubstrateUtil',
'fetchBlocksBatches',
);
) as FetchFunc;
} else {
this.fetchBlocksFunction = fetchFunc;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/node/src/ethereum/block.ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function filterBlocksProcessor(

export function filterTransactionsProcessor(
transaction: EthereumTransaction,
filter: EthereumTransactionFilter,
filter?: EthereumTransactionFilter,
address?: string,
): boolean {
if (!filter) return true;
Expand Down
Loading

0 comments on commit 2b1f2b6

Please sign in to comment.