Skip to content

Commit

Permalink
WIP update to node core with blockchain service
Browse files Browse the repository at this point in the history
  • Loading branch information
stwiname committed Feb 10, 2025
1 parent 97e4af0 commit 88e0eba
Show file tree
Hide file tree
Showing 22 changed files with 953 additions and 1,003 deletions.
14 changes: 7 additions & 7 deletions packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
"subql-node-ethereum": "./bin/run"
},
"dependencies": {
"@nestjs/common": "^9.4.0",
"@nestjs/core": "^9.4.0",
"@nestjs/common": "^11.0.8",
"@nestjs/core": "^11.0.8",
"@nestjs/event-emitter": "^2.0.0",
"@nestjs/platform-express": "^9.4.0",
"@nestjs/schedule": "^3.0.1",
"@nestjs/platform-express": "^11.0.8",
"@nestjs/schedule": "^5.0.1",
"@subql/common": "^5.3.0",
"@subql/common-ethereum": "workspace:*",
"@subql/node-core": "^16.2.0",
"@subql/node-core": "dev",
"@subql/testing": "^2.2.1",
"@subql/types-ethereum": "workspace:*",
"cacheable-lookup": "6",
Expand All @@ -42,8 +42,8 @@
"@subql/utils": "*"
},
"devDependencies": {
"@nestjs/schematics": "^9.2.0",
"@nestjs/testing": "^9.4.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.8",
"@types/express": "^4.17.13",
"@types/jest": "^27.4.0",
"@types/lodash": "^4.14.178",
Expand Down
153 changes: 153 additions & 0 deletions packages/node/src/blockchain.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright 2020-2025 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import { Inject } from '@nestjs/common';
import {
EthereumHandlerKind,
EthereumRuntimeDataSourceImpl,
isCustomDs,
isRuntimeDs,
SubqlEthereumDataSource,
} from '@subql/common-ethereum';
import {
DatasourceParams,
Header,
IBlock,
IBlockchainService,
} from '@subql/node-core';
import {
EthereumBlock,
LightEthereumBlock,
SubqlCustomDatasource,
SubqlCustomHandler,
SubqlMapping,
} from '@subql/types-ethereum';
import { plainToClass } from 'class-transformer';
import { validateSync } from 'class-validator';
import { SubqueryProject } from './configure/SubqueryProject';
import { EthereumApiService } from './ethereum';
import SafeEthProvider from './ethereum/safe-api';
import { calcInterval, ethereumBlockToHeader } from './ethereum/utils.ethereum';
import { BlockContent, getBlockSize } from './indexer/types';
import { IIndexerWorker } from './indexer/worker/worker';

const BLOCK_TIME_VARIANCE = 5000;

const INTERVAL_PERCENT = 0.9;

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { version: packageVersion } = require('../package.json');

export class BlockchainService
implements
IBlockchainService<
SubqlEthereumDataSource,
SubqlCustomDatasource,
SubqueryProject,
SafeEthProvider,
LightEthereumBlock,
EthereumBlock,
IIndexerWorker
>
{
blockHandlerKind = EthereumHandlerKind.Block;
isCustomDs = isCustomDs;
isRuntimeDs = isRuntimeDs;
packageVersion = packageVersion;

constructor(@Inject('APIService') private apiService: EthereumApiService) {}

async fetchBlocks(
blockNums: number[],
): Promise<IBlock<EthereumBlock>[] | IBlock<LightEthereumBlock>[]> {
return this.apiService.fetchBlocks(blockNums);
}

async fetchBlockWorker(
worker: IIndexerWorker,
blockNum: number,
context: { workers: IIndexerWorker[] },
): Promise<Header> {
return worker.fetchBlock(blockNum, context);
}

getBlockSize(block: IBlock<BlockContent>): number {
return getBlockSize(block.block);
}

async getFinalizedHeader(): Promise<Header> {
const block = await this.apiService.api.getFinalizedBlock();
return ethereumBlockToHeader(block);
}

async getBestHeight(): Promise<number> {
return this.apiService.api.getBestBlockHeight();
}

// eslint-disable-next-line @typescript-eslint/require-await
async getChainInterval(): Promise<number> {
const CHAIN_INTERVAL = calcInterval(this.apiService.api) * INTERVAL_PERCENT;

return Math.min(BLOCK_TIME_VARIANCE, CHAIN_INTERVAL);
}

async getHeaderForHash(hash: string): Promise<Header> {
const block = await this.apiService.api.getBlockByHeightOrHash(hash);
return ethereumBlockToHeader(block);
}

async getHeaderForHeight(height: number): Promise<Header> {
const block = await this.apiService.api.getBlockByHeightOrHash(height);
return ethereumBlockToHeader(block);
}

// eslint-disable-next-line @typescript-eslint/require-await
async getSafeApi(block: BlockContent): Promise<SafeEthProvider> {
return this.apiService.safeApi(block.number);
}

async getBlockTimestamp(height: number): Promise<Date | undefined> {
const block = await this.apiService.unsafeApi.api.getBlock(height);

return new Date(block.timestamp * 1000); // TODO test and make sure its in MS not S
}

// eslint-disable-next-line @typescript-eslint/require-await
async updateDynamicDs(
params: DatasourceParams,
dsObj: SubqlEthereumDataSource | SubqlCustomDatasource,
): Promise<void> {
if (isCustomDs(dsObj)) {
dsObj.processor.options = {
...dsObj.processor.options,
...params.args,
};
// TODO how to retain this functionality
// await this.dsProcessorService.validateCustomDs([dsObj]);
} else if (isRuntimeDs(dsObj)) {
dsObj.options = {
...dsObj.options,
...params.args,
};

const parsedDs = plainToClass(EthereumRuntimeDataSourceImpl, dsObj);

const errors = validateSync(parsedDs, {
whitelist: true,
forbidNonWhitelisted: false,
});
if (errors.length) {
throw new Error(
`Dynamic ds is invalid\n${errors
.map((e) => e.toString())
.join('\n')}`,
);
}
}
// return dsObj;
}

onProjectChange(project: SubqueryProject): Promise<void> | void {
this.apiService.updateBlockFetching();
}
}
12 changes: 10 additions & 2 deletions packages/node/src/ethereum/api.service.ethereum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,23 @@ const prepareApiService = async (
provide: 'ISubqueryProject',
useFactory: () => testSubqueryProject(endpoint),
},
EthereumApiService,
{
provide: EthereumApiService,
useFactory: EthereumApiService.init,
inject: [
'ISubqueryProject',
ConnectionPoolService,
EventEmitterModule,
NodeConfig,
],
},
],
imports: [EventEmitterModule.forRoot()],
}).compile();

const app = module.createNestApplication();
await app.init();
const apiService = app.get(EthereumApiService);
await apiService.init();
return [apiService, app];
};

Expand Down
36 changes: 25 additions & 11 deletions packages/node/src/ethereum/api.service.ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class EthereumApiService extends ApiService<
};
private nodeConfig: EthereumNodeConfig;

constructor(
private constructor(
@Inject('ISubqueryProject') private project: SubqueryProject,
connectionPoolService: ConnectionPoolService<EthereumApiConnection>,
eventEmitter: EventEmitter2,
Expand All @@ -60,31 +60,45 @@ export class EthereumApiService extends ApiService<
this.updateBlockFetching();
}

async init(): Promise<EthereumApiService> {
static async init(
project: SubqueryProject,
connectionPoolService: ConnectionPoolService<EthereumApiConnection>,
eventEmitter: EventEmitter2,
nodeConfig: NodeConfig,
): Promise<EthereumApiService> {
let network: EthereumNetworkConfig;
try {
network = this.project.network;
network = project.network;
} catch (e) {
exitWithError(new Error(`Failed to init api`, { cause: e }), logger);
}

if (this.nodeConfig.primaryNetworkEndpoint) {
const [endpoint, config] = this.nodeConfig.primaryNetworkEndpoint;
if (nodeConfig.primaryNetworkEndpoint) {
const [endpoint, config] = nodeConfig.primaryNetworkEndpoint;
(network.endpoint as Record<string, IEndpointConfig>)[endpoint] = config;
}

await this.createConnections(network, (endpoint, config) =>
const ethNodeConfig = new EthereumNodeConfig(nodeConfig);

const apiService = new EthereumApiService(
project,
connectionPoolService,
eventEmitter,
nodeConfig,
);

await apiService.createConnections(network, (endpoint, config) =>
EthereumApiConnection.create(
endpoint,
this.nodeConfig.blockConfirmations,
this.fetchBlocksBatches,
this.eventEmitter,
this.nodeConfig.unfinalizedBlocks,
ethNodeConfig.blockConfirmations,
apiService.fetchBlocksBatches,
eventEmitter,
nodeConfig.unfinalizedBlocks,
config,
),
);

return this;
return apiService;
}

protected metadataMismatchError(
Expand Down

This file was deleted.

This file was deleted.

12 changes: 0 additions & 12 deletions packages/node/src/indexer/blockDispatcher/index.ts

This file was deleted.

Loading

0 comments on commit 88e0eba

Please sign in to comment.