From f362a60855e82e7417a3c8eedb1be003ddb3418d Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Wed, 14 Aug 2024 15:40:12 +0800 Subject: [PATCH 01/43] feat: auto close outdated channel --- .../src/payg/payg.service.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/apps/indexer-coordinator/src/payg/payg.service.ts b/apps/indexer-coordinator/src/payg/payg.service.ts index d3247df55..4349b3e16 100644 --- a/apps/indexer-coordinator/src/payg/payg.service.ts +++ b/apps/indexer-coordinator/src/payg/payg.service.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { Injectable } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; import { StateChannel as StateChannelOnChain } from '@subql/contract-sdk'; import { bytes32ToCid } from '@subql/network-clients'; @@ -538,4 +539,30 @@ export class PaygService { async getOpenChannels() { return await this.channelRepo.find({ where: { status: ChannelStatus.OPEN } }); } + + @Cron(CronExpression.EVERY_MINUTE) + async closeOutdatedAndNotExtended() { + const channels = await this.channelRepo.find(); + for (const c of channels) { + try { + const now = Math.floor(Date.now() / 1000); + const channelState = await this.channelFromContract(BigNumber.from(c.id)); + + // terminating + if ( + !channelState || + channelState.status === ChannelStatus.TERMINATING || + c.expiredAt <= now || + c.status === ChannelStatus.TERMINATING + ) { + continue; + // return this.updateChannelFromNetwork(c.id, altChannelData, true); + } + // outdated + await this.terminate(c.id); + } catch (e) { + logger.debug(`closeOutdatedAndNotExtended state channel error: ${c.id}`); + } + } + } } From 99feaf02f53f8d7bf77c401fb08e3ee718e9f2ac Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Mon, 2 Sep 2024 16:15:15 +0800 Subject: [PATCH 02/43] feat: test --- .../src/payg/payg.service.ts | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/indexer-coordinator/src/payg/payg.service.ts b/apps/indexer-coordinator/src/payg/payg.service.ts index 4349b3e16..3f2578f32 100644 --- a/apps/indexer-coordinator/src/payg/payg.service.ts +++ b/apps/indexer-coordinator/src/payg/payg.service.ts @@ -1,7 +1,7 @@ // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { Injectable } from '@nestjs/common'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; import { StateChannel as StateChannelOnChain } from '@subql/contract-sdk'; @@ -27,7 +27,7 @@ export type ChannelState = StateChannelOnChain.ChannelStateStructOutput; const logger = getLogger('payg'); @Injectable() -export class PaygService { +export class PaygService implements OnModuleInit { constructor( @InjectRepository(Channel) private channelRepo: Repository, @InjectRepository(PaygEntity) private paygRepo: Repository, @@ -38,6 +38,10 @@ export class PaygService { private account: AccountService ) {} + onModuleInit() { + // this.closeOutdatedAndNotExtended(); + } + async channelFromContract(id: BigNumber): Promise { const channel = await this.contract.getSdk().stateChannel.channel(id); return channel?.indexer !== ZERO_ADDRESS ? channel : undefined; @@ -445,6 +449,9 @@ export class PaygService { if (channel.onchain === channel.remote) { return channel; } + if (!channel.lastIndexerSign || !channel.lastConsumerSign) { + return channel; + } // terminate await this.contract.sendTransaction({ @@ -540,28 +547,20 @@ export class PaygService { return await this.channelRepo.find({ where: { status: ChannelStatus.OPEN } }); } - @Cron(CronExpression.EVERY_MINUTE) + @Cron(CronExpression.EVERY_10_MINUTES) async closeOutdatedAndNotExtended() { const channels = await this.channelRepo.find(); for (const c of channels) { try { const now = Math.floor(Date.now() / 1000); - const channelState = await this.channelFromContract(BigNumber.from(c.id)); - - // terminating - if ( - !channelState || - channelState.status === ChannelStatus.TERMINATING || - c.expiredAt <= now || - c.status === ChannelStatus.TERMINATING - ) { + if (c.expiredAt > now || c.status === ChannelStatus.TERMINATING) { continue; - // return this.updateChannelFromNetwork(c.id, altChannelData, true); } - // outdated await this.terminate(c.id); } catch (e) { - logger.debug(`closeOutdatedAndNotExtended state channel error: ${c.id}`); + logger.error( + `closeOutdatedAndNotExtended state channel(${c.id} ${c.deploymentId}) error: ${e.stack}` + ); } } } From b52e78f88bbbc87a05612cdccab0cc472e2d7bf4 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Wed, 11 Sep 2024 17:59:23 +0800 Subject: [PATCH 03/43] feat: evm http metrics --- .../src/project/project.rpc.service.ts | 4 +- .../src/project/rpc.factory.ts | 41 ++++++++++++++++--- apps/indexer-coordinator/src/project/types.ts | 7 ++++ apps/indexer-coordinator/src/utils/json.ts | 10 +++++ 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 apps/indexer-coordinator/src/utils/json.ts diff --git a/apps/indexer-coordinator/src/project/project.rpc.service.ts b/apps/indexer-coordinator/src/project/project.rpc.service.ts index 929b19219..74561716b 100644 --- a/apps/indexer-coordinator/src/project/project.rpc.service.ts +++ b/apps/indexer-coordinator/src/project/project.rpc.service.ts @@ -19,7 +19,7 @@ import { } from './project.model'; import { ProjectService } from './project.service'; import { RequiredRpcType, getRpcFamilyObject } from './rpc.factory'; -import { AccessType, ProjectType } from './types'; +import { AccessType, ENDPOINT_KEY, ProjectType } from './types'; const logger = getLogger('project.rpc.service'); @@ -202,7 +202,7 @@ export class ProjectRpcService { const projectManifest = project.manifest as RpcManifest; const rpcFamily = projectManifest.rpcFamily.find((family) => endpointKey.startsWith(family)); // const protocolType = endpointKey.replace(rpcFamily, '').toLowerCase(); - await getRpcFamilyObject(rpcFamily) + await getRpcFamilyObject(rpcFamily, endpointKey as ENDPOINT_KEY) .withChainId(projectManifest.chain?.chainId) .withGenesisHash(projectManifest.chain?.genesisHash) .withNodeType(projectManifest.nodeType) diff --git a/apps/indexer-coordinator/src/project/rpc.factory.ts b/apps/indexer-coordinator/src/project/rpc.factory.ts index b18e6e5bb..8557dccb9 100644 --- a/apps/indexer-coordinator/src/project/rpc.factory.ts +++ b/apps/indexer-coordinator/src/project/rpc.factory.ts @@ -7,14 +7,19 @@ import _ from 'lodash'; import * as semver from 'semver'; import { getLogger } from 'src/utils/logger'; import { WebSocket } from 'ws'; +import { ENDPOINT_KEY } from './types'; +import { safeJSONParse } from 'src/utils/json'; const logger = getLogger('rpc.factory'); -export function getRpcFamilyObject(rpcFamily: string): IRpcFamily | undefined { +export function getRpcFamilyObject( + rpcFamily: string, + endpointKey: ENDPOINT_KEY +): IRpcFamily | undefined { let family: IRpcFamily; switch (rpcFamily) { case 'evm': - family = new RpcFamilyEvm(); + family = new RpcFamilyEvm(endpointKey); break; case 'substrate': family = new RpcFamilyPolkadot(); @@ -98,6 +103,8 @@ async function jsonWsRpcRequest(endpoint: string, method: string, params: any[]) } } +async function jsonMetricsRpcRequest(endpoint: string) {} + function getRpcRequestFunction(endpoint: string) { if (!endpoint) { throw new Error('Endpoint is empty'); @@ -130,6 +137,11 @@ abstract class RpcFamily implements IRpcFamily { protected actions: (() => Promise)[] = []; protected endpoint: string; protected requiredRpcType: RequiredRpcType = RequiredRpcType.http; + protected targetEndpointKey: ENDPOINT_KEY; + + constructor(targetEndpointKey: ENDPOINT_KEY) { + this.targetEndpointKey = targetEndpointKey; + } async validate(endpoint: string) { this.endpoint = endpoint; @@ -172,16 +184,35 @@ abstract class RpcFamily implements IRpcFamily { export class RpcFamilyEvm extends RpcFamily { getEndpointKeys(): string[] { - return ['evmWs', 'evmHttp']; + return [ENDPOINT_KEY.evmWs, ENDPOINT_KEY.evmHttp, ENDPOINT_KEY.evmHttpMetrics]; } withChainId(chainId: string): IRpcFamily { this.actions.push(async () => { - const result = await getRpcRequestFunction(this.endpoint)(this.endpoint, 'eth_chainId', []); + let p = null; + switch (this.targetEndpointKey) { + case ENDPOINT_KEY.evmHttp: + p = jsonRpcRequest(this.endpoint, 'eth_chainId', []); + break; + case ENDPOINT_KEY.evmWs: + p = jsonWsRpcRequest(this.endpoint, 'eth_chainId', []); + break; + case ENDPOINT_KEY.evmHttpMetrics: + p = jsonMetricsRpcRequest(this.endpoint); + break; + default: + throw new Error('Invalid endpointKey'); + } + const result = await p; if (result.data.error) { throw new Error(`Request eth_chainId failed: ${result.data.error.message}`); } - const chainIdFromRpc = result.data.result; + let chainIdFromRpc = result.data.result; + if (this.targetEndpointKey === ENDPOINT_KEY.evmHttpMetrics) { + const info = safeJSONParse(result.data['chain/info']); + chainIdFromRpc = info?.chain_id; + } + if (!BigNumber.from(chainIdFromRpc).eq(BigNumber.from(chainId || 0))) { throw new Error( `ChainId mismatch: ${BigNumber.from(chainIdFromRpc).toString()} != ${BigNumber.from( diff --git a/apps/indexer-coordinator/src/project/types.ts b/apps/indexer-coordinator/src/project/types.ts index 47143b5cc..8fe931542 100644 --- a/apps/indexer-coordinator/src/project/types.ts +++ b/apps/indexer-coordinator/src/project/types.ts @@ -126,3 +126,10 @@ export type TemplateType = { pgKey?: string; pgCert?: string; }; + + +export enum ENDPOINT_KEY { + evmHttp = 'evmHttp', + evmWs = 'evmWs', + evmHttpMetrics = 'evmHttpMetrics', +} \ No newline at end of file diff --git a/apps/indexer-coordinator/src/utils/json.ts b/apps/indexer-coordinator/src/utils/json.ts new file mode 100644 index 000000000..db67e8786 --- /dev/null +++ b/apps/indexer-coordinator/src/utils/json.ts @@ -0,0 +1,10 @@ +// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +export function safeJSONParse(data: string) { + try { + return JSON.parse(data); + } catch (error) { + return null; + } +} From e8a5f0599d793e88b3c23675f31d9022a91ed377 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Thu, 12 Sep 2024 18:05:20 +0800 Subject: [PATCH 04/43] feat: polkadot --- .../src/project/project.model.ts | 2 + .../src/project/project.rpc.service.ts | 71 +++++++++------- .../src/project/rpc.factory.ts | 81 +++++++++++++------ apps/indexer-coordinator/src/project/types.ts | 27 ++++++- 4 files changed, 127 insertions(+), 54 deletions(-) diff --git a/apps/indexer-coordinator/src/project/project.model.ts b/apps/indexer-coordinator/src/project/project.model.ts index 9ee446da6..5f5711190 100644 --- a/apps/indexer-coordinator/src/project/project.model.ts +++ b/apps/indexer-coordinator/src/project/project.model.ts @@ -48,6 +48,8 @@ export class ValidationResponse { valid: boolean; @Field() reason: string; + @Field() + level: string; } @ObjectType('Metadata') diff --git a/apps/indexer-coordinator/src/project/project.rpc.service.ts b/apps/indexer-coordinator/src/project/project.rpc.service.ts index 74561716b..68ea1ece1 100644 --- a/apps/indexer-coordinator/src/project/project.rpc.service.ts +++ b/apps/indexer-coordinator/src/project/project.rpc.service.ts @@ -19,7 +19,7 @@ import { } from './project.model'; import { ProjectService } from './project.service'; import { RequiredRpcType, getRpcFamilyObject } from './rpc.factory'; -import { AccessType, ENDPOINT_KEY, ProjectType } from './types'; +import { AccessType, ENDPOINT_KEY, ErrorLevel, ProjectType } from './types'; const logger = getLogger('project.rpc.service'); @@ -69,6 +69,7 @@ export class ProjectRpcService { const validateUrlResult = this.validateRpcEndpointsUrl(serviceEndpoints); serviceEndpoints = serviceEndpoints.filter((endpoint) => endpoint.value); let reason = ''; + let errorLevel = ErrorLevel.none; for (const endpoint of serviceEndpoints) { if (!validateUrlResult.valid) { endpoint.valid = false; @@ -82,45 +83,60 @@ export class ProjectRpcService { `Project ${project.id} endpoint ${endpoint.key} is invalid: ${response.reason}` ); } + endpoint.valid = response.valid; endpoint.reason = response.reason; - reason = reason || response.reason; + + if (response.level === ErrorLevel.error) { + reason = reason || response.reason; + errorLevel = errorLevel || response.level; + } } - return this.formatResponse(!reason, reason); + return this.formatResponse(!reason, reason, errorLevel); } private validateRpcEndpointsUrl(serviceEndpoints: SeviceEndpoint[]): ValidationResponse { if (!serviceEndpoints || serviceEndpoints.length === 0) { - return this.formatResponse(false, 'No endpoints'); + return this.formatResponse(false, 'No endpoints', ErrorLevel.error); } - const rpcFamily = serviceEndpoints[0].key.replace(/(Http|Ws)$/, ''); - if (serviceEndpoints.length > 1) { - const rpcFamily2 = serviceEndpoints[1].key.replace(/(Http|Ws)$/, ''); + const rpcFamily = serviceEndpoints[0].key.replace(/(Http|Ws|MetricsHttp)$/, ''); + + for (let i = 1; i < serviceEndpoints.length; i++) { + const rpcFamily2 = serviceEndpoints[i].key.replace(/(Http|Ws|MetricsHttp)$/, ''); if (rpcFamily !== rpcFamily2) { - return this.formatResponse(false, 'Endpoints are not from the same rpc family'); + return this.formatResponse( + false, + 'Endpoints are not from the same rpc family', + ErrorLevel.error + ); } try { const host1 = new URL(serviceEndpoints[0].value).hostname; - const host2 = new URL(serviceEndpoints[1].value).hostname; + const host2 = new URL(serviceEndpoints[i].value).hostname; if (host1 !== host2) { - return this.formatResponse(false, 'Endpoints are not from the same host'); + return this.formatResponse( + false, + 'Endpoints are not from the same host', + ErrorLevel.error + ); } } catch (e) { - return this.formatResponse(false, 'Invalid url'); + return this.formatResponse(false, 'Invalid url', ErrorLevel.error); } } + for (const endpoint of serviceEndpoints) { if ( endpoint.key.endsWith('Http') && !(endpoint.value.startsWith('http://') || endpoint.value.startsWith('https://')) ) { - return this.formatResponse(false, 'Invalid http endpoint'); + return this.formatResponse(false, 'Invalid http endpoint', ErrorLevel.error); } if ( endpoint.key.endsWith('Ws') && !(endpoint.value.startsWith('ws://') || endpoint.value.startsWith('wss://')) ) { - return this.formatResponse(false, 'Invalid ws endpoint'); + return this.formatResponse(false, 'Invalid ws endpoint', ErrorLevel.error); } } return this.validateRequiredRpcType(rpcFamily, serviceEndpoints); @@ -134,12 +150,12 @@ export class ProjectRpcService { switch (rpcType) { case RequiredRpcType.http: if (!serviceEndpoints.find((endpoint) => endpoint.key.endsWith('Http'))) { - return this.formatResponse(false, 'Missing http endpoint'); + return this.formatResponse(false, 'Missing http endpoint', ErrorLevel.error); } break; case RequiredRpcType.ws: if (!serviceEndpoints.find((endpoint) => endpoint.key.endsWith('Ws'))) { - return this.formatResponse(false, 'Missing ws endpoint'); + return this.formatResponse(false, 'Missing ws endpoint', ErrorLevel.error); } break; case RequiredRpcType.any: @@ -148,7 +164,7 @@ export class ProjectRpcService { (endpoint) => endpoint.key.endsWith('Http') || endpoint.key.endsWith('Ws') ) ) { - return this.formatResponse(false, 'Missing http or ws endpoint'); + return this.formatResponse(false, 'Missing http or ws endpoint', ErrorLevel.error); } break; case RequiredRpcType.both: @@ -156,11 +172,11 @@ export class ProjectRpcService { !serviceEndpoints.find((endpoint) => endpoint.key.endsWith('Http')) || !serviceEndpoints.find((endpoint) => endpoint.key.endsWith('Ws')) ) { - return this.formatResponse(false, 'Missing http and ws endpoint'); + return this.formatResponse(false, 'Missing http and ws endpoint', ErrorLevel.error); } break; default: - return this.formatResponse(false, 'Unknown rpc type'); + return this.formatResponse(false, 'Unknown rpc type', ErrorLevel.error); } return this.formatResponse(true); } @@ -174,7 +190,7 @@ export class ProjectRpcService { try { const domain = getDomain(endpoint); if (!domain) { - return this.formatResponse(false, 'Invalid domain'); + return this.formatResponse(false, 'Invalid domain', ErrorLevel.error); } let ip: string; if (isIp(domain)) { @@ -183,14 +199,14 @@ export class ProjectRpcService { ip = await getIpAddress(domain); } if (!ip) { - return this.formatResponse(false, 'Invalid ip address'); + return this.formatResponse(false, 'Invalid ip address', ErrorLevel.error); } if (!isPrivateIp(ip)) { - return this.formatResponse(false, 'Endpoint is not private ip'); + return this.formatResponse(false, 'Endpoint is not private ip', ErrorLevel.error); } } catch (e) { logger.error(e); - return this.formatResponse(false, e.message); + return this.formatResponse(false, e.message, ErrorLevel.error); } // compare chain id, genesis hash, rpc family, client name and version, node type @@ -202,23 +218,24 @@ export class ProjectRpcService { const projectManifest = project.manifest as RpcManifest; const rpcFamily = projectManifest.rpcFamily.find((family) => endpointKey.startsWith(family)); // const protocolType = endpointKey.replace(rpcFamily, '').toLowerCase(); - await getRpcFamilyObject(rpcFamily, endpointKey as ENDPOINT_KEY) + await getRpcFamilyObject(rpcFamily) .withChainId(projectManifest.chain?.chainId) .withGenesisHash(projectManifest.chain?.genesisHash) .withNodeType(projectManifest.nodeType) .withClientNameAndVersion(projectManifest.client?.name, projectManifest.client?.version) - .validate(endpoint); + .validate(endpoint, endpointKey as ENDPOINT_KEY); return this.formatResponse(true); } catch (e) { logger.debug(e); - return this.formatResponse(false, e.message); + return this.formatResponse(false, e.message, e.level || ErrorLevel.error); } } - private formatResponse(valid = false, reason = ''): ValidationResponse { + private formatResponse(valid = false, reason = '', level = ErrorLevel.none): ValidationResponse { return { valid, reason, + level, }; } @@ -244,7 +261,7 @@ export class ProjectRpcService { projectConfig.serviceEndpoints = project.serviceEndpoints; const validateResult = await this.validateProjectEndpoints(project, project.serviceEndpoints); - if (!validateResult.valid) { + if (!validateResult.valid && validateResult.level === ErrorLevel.error) { throw new Error(`Invalid endpoints: ${validateResult.reason}`); } diff --git a/apps/indexer-coordinator/src/project/rpc.factory.ts b/apps/indexer-coordinator/src/project/rpc.factory.ts index 8557dccb9..b466669ef 100644 --- a/apps/indexer-coordinator/src/project/rpc.factory.ts +++ b/apps/indexer-coordinator/src/project/rpc.factory.ts @@ -5,21 +5,18 @@ import axios from 'axios'; import { BigNumber } from 'ethers'; import _ from 'lodash'; import * as semver from 'semver'; +import { safeJSONParse } from 'src/utils/json'; import { getLogger } from 'src/utils/logger'; import { WebSocket } from 'ws'; -import { ENDPOINT_KEY } from './types'; -import { safeJSONParse } from 'src/utils/json'; +import { ENDPOINT_KEY, ErrorLevel, ValidateRpcEndpointError } from './types'; const logger = getLogger('rpc.factory'); -export function getRpcFamilyObject( - rpcFamily: string, - endpointKey: ENDPOINT_KEY -): IRpcFamily | undefined { +export function getRpcFamilyObject(rpcFamily: string): IRpcFamily | undefined { let family: IRpcFamily; switch (rpcFamily) { case 'evm': - family = new RpcFamilyEvm(endpointKey); + family = new RpcFamilyEvm(); break; case 'substrate': family = new RpcFamilyPolkadot(); @@ -103,7 +100,16 @@ async function jsonWsRpcRequest(endpoint: string, method: string, params: any[]) } } -async function jsonMetricsRpcRequest(endpoint: string) {} +async function jsonMetricsHttpRpcRequest(endpoint: string) { + if (!endpoint) { + throw new Error('Endpoint is empty'); + } + return axios.request({ + url: endpoint, + method: 'get', + timeout: 1000 * 10, + }); +} function getRpcRequestFunction(endpoint: string) { if (!endpoint) { @@ -126,7 +132,7 @@ export interface IRpcFamily { withNodeType(nodeType: string): IRpcFamily; withClientNameAndVersion(clientName: string, clientVersion: string): IRpcFamily; withClientVersion(clientVersion: string): IRpcFamily; - validate(endpoint: string): Promise; + validate(endpoint: string, endpointKey: ENDPOINT_KEY): Promise; getStartHeight(endpoint: string): Promise; getTargetHeight(endpoint: string): Promise; getLastHeight(endpoint: string): Promise; @@ -139,12 +145,9 @@ abstract class RpcFamily implements IRpcFamily { protected requiredRpcType: RequiredRpcType = RequiredRpcType.http; protected targetEndpointKey: ENDPOINT_KEY; - constructor(targetEndpointKey: ENDPOINT_KEY) { - this.targetEndpointKey = targetEndpointKey; - } - - async validate(endpoint: string) { + async validate(endpoint: string, endpointKey: ENDPOINT_KEY) { this.endpoint = endpoint; + this.targetEndpointKey = endpointKey; await Promise.all(this.actions.map((action) => action())); } getEndpointKeys(): string[] { @@ -184,12 +187,13 @@ abstract class RpcFamily implements IRpcFamily { export class RpcFamilyEvm extends RpcFamily { getEndpointKeys(): string[] { - return [ENDPOINT_KEY.evmWs, ENDPOINT_KEY.evmHttp, ENDPOINT_KEY.evmHttpMetrics]; + return [ENDPOINT_KEY.evmWs, ENDPOINT_KEY.evmHttp, ENDPOINT_KEY.evmMetricsHttp]; } withChainId(chainId: string): IRpcFamily { this.actions.push(async () => { let p = null; + let errorLevel = ErrorLevel.error; switch (this.targetEndpointKey) { case ENDPOINT_KEY.evmHttp: p = jsonRpcRequest(this.endpoint, 'eth_chainId', []); @@ -197,27 +201,32 @@ export class RpcFamilyEvm extends RpcFamily { case ENDPOINT_KEY.evmWs: p = jsonWsRpcRequest(this.endpoint, 'eth_chainId', []); break; - case ENDPOINT_KEY.evmHttpMetrics: - p = jsonMetricsRpcRequest(this.endpoint); + case ENDPOINT_KEY.evmMetricsHttp: + p = jsonMetricsHttpRpcRequest(this.endpoint); + errorLevel = ErrorLevel.warn; break; default: - throw new Error('Invalid endpointKey'); + throw new ValidateRpcEndpointError('Invalid endpointKey', errorLevel); } const result = await p; if (result.data.error) { - throw new Error(`Request eth_chainId failed: ${result.data.error.message}`); + throw new ValidateRpcEndpointError( + `Request eth_chainId failed: ${result.data.error.message}`, + errorLevel + ); } let chainIdFromRpc = result.data.result; - if (this.targetEndpointKey === ENDPOINT_KEY.evmHttpMetrics) { + if (this.targetEndpointKey === ENDPOINT_KEY.evmMetricsHttp) { const info = safeJSONParse(result.data['chain/info']); chainIdFromRpc = info?.chain_id; } if (!BigNumber.from(chainIdFromRpc).eq(BigNumber.from(chainId || 0))) { - throw new Error( + throw new ValidateRpcEndpointError( `ChainId mismatch: ${BigNumber.from(chainIdFromRpc).toString()} != ${BigNumber.from( chainId - ).toString()}` + ).toString()}`, + errorLevel ); } }); @@ -226,6 +235,10 @@ export class RpcFamilyEvm extends RpcFamily { withGenesisHash(genesisHash: string): IRpcFamily { this.actions.push(async () => { + if (this.targetEndpointKey === ENDPOINT_KEY.evmMetricsHttp) { + return; + } + const result = await getRpcRequestFunction(this.endpoint)( this.endpoint, 'eth_getBlockByNumber', @@ -247,6 +260,9 @@ export class RpcFamilyEvm extends RpcFamily { withNodeType(nodeType: string): IRpcFamily { this.actions.push(async () => { + if (this.targetEndpointKey === ENDPOINT_KEY.evmMetricsHttp) { + return; + } const result = await getRpcRequestFunction(this.endpoint)(this.endpoint, 'eth_getBalance', [ '0x0000000000000000000000000000000000000000', _.toLower(nodeType) === 'archive' ? '0x1' : 'latest', @@ -263,6 +279,9 @@ export class RpcFamilyEvm extends RpcFamily { if (!clientName && !clientVersion) { return; } + if (this.targetEndpointKey === ENDPOINT_KEY.evmMetricsHttp) { + return; + } const result = await getRpcRequestFunction(this.endpoint)( this.endpoint, 'web3_clientVersion', @@ -349,7 +368,7 @@ export class RpcFamilySubstrate extends RpcFamily { } getEndpointKeys(): string[] { - return ['substrateWs', 'substrateHttp']; + return [ENDPOINT_KEY.substrateWs, ENDPOINT_KEY.substrateHttp]; } withChainId(chainId: string): IRpcFamily { @@ -460,6 +479,20 @@ export class RpcFamilySubstrate extends RpcFamily { export class RpcFamilyPolkadot extends RpcFamilySubstrate { getEndpointKeys(): string[] { - return ['polkadotWs', 'polkadotHttp']; + return [ENDPOINT_KEY.polkadotWs, ENDPOINT_KEY.polkadotHttp, ENDPOINT_KEY.polkadotMetricsHttp]; + } + + withGenesisHash(genesisHash: string): IRpcFamily { + if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { + return this; + } + return super.withGenesisHash(genesisHash); + } + + withNodeType(nodeType: string): IRpcFamily { + if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { + return this; + } + return super.withNodeType(nodeType); } } diff --git a/apps/indexer-coordinator/src/project/types.ts b/apps/indexer-coordinator/src/project/types.ts index 8fe931542..17ecc5653 100644 --- a/apps/indexer-coordinator/src/project/types.ts +++ b/apps/indexer-coordinator/src/project/types.ts @@ -127,9 +127,30 @@ export type TemplateType = { pgCert?: string; }; - export enum ENDPOINT_KEY { evmHttp = 'evmHttp', evmWs = 'evmWs', - evmHttpMetrics = 'evmHttpMetrics', -} \ No newline at end of file + evmMetricsHttp = 'evmMetricsHttp', + + polkadotWs = 'polkadotWs', + polkadotHttp = 'polkadotHttp', + polkadotMetricsHttp = 'polkadotMetricsHttp', + + substrateWs = 'substrateWs', + substrateHttp = 'substrateHttp', +} + +export enum ErrorLevel { + none = '', + warn = 'warn', + error = 'error', +} + +export class ValidateRpcEndpointError extends Error { + level: string; + constructor(message: string, level: string = ErrorLevel.none) { + super(message); + this.name = 'ValidateRpcEndpointError'; + this.level = level; + } +} From 158ddc0e475f5680782132de2a196c2a01e6aa40 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Fri, 13 Sep 2024 10:39:15 +0800 Subject: [PATCH 05/43] fix: error level --- apps/indexer-coordinator/src/project/project.model.ts | 4 ++-- .../src/project/validator/common.validator.ts | 9 +++++---- .../src/project/validator/subquery.validator.ts | 11 ++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/indexer-coordinator/src/project/project.model.ts b/apps/indexer-coordinator/src/project/project.model.ts index 5f5711190..871d56fde 100644 --- a/apps/indexer-coordinator/src/project/project.model.ts +++ b/apps/indexer-coordinator/src/project/project.model.ts @@ -48,8 +48,8 @@ export class ValidationResponse { valid: boolean; @Field() reason: string; - @Field() - level: string; + @Field({ nullable: true }) + level?: string; } @ObjectType('Metadata') diff --git a/apps/indexer-coordinator/src/project/validator/common.validator.ts b/apps/indexer-coordinator/src/project/validator/common.validator.ts index 48ef9ff46..b4c2e809d 100644 --- a/apps/indexer-coordinator/src/project/validator/common.validator.ts +++ b/apps/indexer-coordinator/src/project/validator/common.validator.ts @@ -4,6 +4,7 @@ import { getDomain, getIpAddress, isIp, isPrivateIp } from 'src/utils/network'; import { getLogger } from '../../utils/logger'; import { ValidationResponse } from '../project.model'; +import { ErrorLevel } from '../types'; const logger = getLogger('common.validator'); @@ -11,7 +12,7 @@ export async function validatePrivateEndpoint(endpoint: string): Promise Date: Fri, 13 Sep 2024 16:42:49 +0800 Subject: [PATCH 06/43] feat: verify polkadot height --- .../src/project/project.rpc.service.ts | 1 + .../src/project/rpc.factory.ts | 80 +++++++++++++++++-- 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/apps/indexer-coordinator/src/project/project.rpc.service.ts b/apps/indexer-coordinator/src/project/project.rpc.service.ts index 68ea1ece1..2394c1319 100644 --- a/apps/indexer-coordinator/src/project/project.rpc.service.ts +++ b/apps/indexer-coordinator/src/project/project.rpc.service.ts @@ -222,6 +222,7 @@ export class ProjectRpcService { .withChainId(projectManifest.chain?.chainId) .withGenesisHash(projectManifest.chain?.genesisHash) .withNodeType(projectManifest.nodeType) + .withHeight() .withClientNameAndVersion(projectManifest.client?.name, projectManifest.client?.version) .validate(endpoint, endpointKey as ENDPOINT_KEY); return this.formatResponse(true); diff --git a/apps/indexer-coordinator/src/project/rpc.factory.ts b/apps/indexer-coordinator/src/project/rpc.factory.ts index b466669ef..4e58bfc27 100644 --- a/apps/indexer-coordinator/src/project/rpc.factory.ts +++ b/apps/indexer-coordinator/src/project/rpc.factory.ts @@ -132,6 +132,7 @@ export interface IRpcFamily { withNodeType(nodeType: string): IRpcFamily; withClientNameAndVersion(clientName: string, clientVersion: string): IRpcFamily; withClientVersion(clientVersion: string): IRpcFamily; + withHeight(height?: number): IRpcFamily; validate(endpoint: string, endpointKey: ENDPOINT_KEY): Promise; getStartHeight(endpoint: string): Promise; getTargetHeight(endpoint: string): Promise; @@ -148,7 +149,12 @@ abstract class RpcFamily implements IRpcFamily { async validate(endpoint: string, endpointKey: ENDPOINT_KEY) { this.endpoint = endpoint; this.targetEndpointKey = endpointKey; - await Promise.all(this.actions.map((action) => action())); + + while (this.actions.length) { + const action = this.actions.shift(); + await action(); + } + // await Promise.all(this.actions.map((action) => action())); } getEndpointKeys(): string[] { throw new Error('Method not implemented.'); @@ -171,6 +177,9 @@ abstract class RpcFamily implements IRpcFamily { withClientVersion(clientVersion: string): IRpcFamily { throw new Error('Method not implemented.'); } + withHeight(height?: number): IRpcFamily { + throw new Error('Method not implemented.'); + } getStartHeight(endpoint: string): Promise { throw new Error('Method not implemented.'); } @@ -306,6 +315,10 @@ export class RpcFamilyEvm extends RpcFamily { return this; } + withHeight(height?: number): IRpcFamily { + return this; + } + async getStartHeight(endpoint: string): Promise { const result = await getRpcRequestFunction(endpoint)(endpoint, 'eth_syncing', []); if (result.data.error) { @@ -431,6 +444,10 @@ export class RpcFamilySubstrate extends RpcFamily { return this; } + withHeight(height?: number): IRpcFamily { + return this; + } + async getStartHeight(endpoint: string): Promise { if (this.startHeight) { return Promise.resolve(this.startHeight); @@ -483,16 +500,63 @@ export class RpcFamilyPolkadot extends RpcFamilySubstrate { } withGenesisHash(genesisHash: string): IRpcFamily { - if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { - return this; - } - return super.withGenesisHash(genesisHash); + this.actions.push(async () => { + if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { + return this; + } + return super.withGenesisHash(genesisHash); + }); + return this; } withNodeType(nodeType: string): IRpcFamily { - if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { - return this; + this.actions.push(async () => { + if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { + return this; + } + return super.withNodeType(nodeType); + }); + return this; + } + + withHeight(height?: number): IRpcFamily { + this.actions.push(async () => { + if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { + const result = await jsonMetricsHttpRpcRequest(this.endpoint); + if (result.data.error) { + throw new ValidateRpcEndpointError( + `Request metrics failed: ${result.data.error.message}`, + ErrorLevel.warn + ); + } + const height = this.parseBestBlockHeight(result.data); + if (!Number(height)) { + throw new ValidateRpcEndpointError( + `parse metrics height fail. current: ${height}`, + ErrorLevel.warn + ); + } + } + }); + return this; + } + + parseBestBlockHeight(metrics: string) { + for (const line of metrics.split('\n')) { + if (line.startsWith('substrate_block_height')) { + const match = line.slice(22).match(/\{(.*)\}/); + if (match) { + const jsonObject: { [key: string]: string } = {}; + for (const pair of match[1].split(',')) { + const [key, value] = pair.split('='); + jsonObject[key.trim()] = value.replace(/"/g, '').trim(); + } + if (jsonObject.status === 'best') { + return line.split(/\s+/).pop(); + } + } + } } - return super.withNodeType(nodeType); + return ''; } } From ef6bba983ab83b50e7c02300c6ea79c96ebea3ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:56:10 +0800 Subject: [PATCH 07/43] build(deps): update tokio-tungstenite requirement from 0.23.0 to 0.24.0 (#505) --- updated-dependencies: - dependency-name: tokio-tungstenite dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/indexer-proxy/proxy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index 2e9cdbaf4..2350a877a 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -36,6 +36,6 @@ tokenizers = "0.20" tokio = { version = "1", features = ["full"] } tower-http = { version = "0.5", features = ["cors"] } tokio-stream = { version = "0.1" } -tokio-tungstenite = { version = "0.23.0", features = ["native-tls"] } +tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] } futures-util= "0.3.30" url = "2.2" From 5087bfeca9b0badc0e091b15518b2cc66005fed5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:59:27 +0800 Subject: [PATCH 08/43] build(deps): update axum-streams requirement from 0.18 to 0.19 (#508) Updates the requirements on [axum-streams](https://github.com/abdolence/axum-streams-rs) to permit the latest version. - [Release notes](https://github.com/abdolence/axum-streams-rs/releases) - [Commits](https://github.com/abdolence/axum-streams-rs/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: axum-streams dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/indexer-proxy/proxy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index 2350a877a..e0920f57d 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -9,7 +9,7 @@ subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = aes-gcm = "0.10" axum = { version = "0.7", features = ["ws"] } axum-auth = "0.7" -axum-streams = { version = "0.18", features = ["text"] } +axum-streams = { version = "0.19", features = ["text"] } base64 = "0.22" bincode = "1.3" chamomile_types = "0.10" From 6ce1cb7d44305758624bee9c357a66291aa303be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:00:44 +0800 Subject: [PATCH 09/43] build(deps): update reqwest-streams requirement from 0.7 to 0.8 (#509) Updates the requirements on [reqwest-streams](https://github.com/abdolence/reqwest-streams-rs) to permit the latest version. - [Release notes](https://github.com/abdolence/reqwest-streams-rs/releases) - [Commits](https://github.com/abdolence/reqwest-streams-rs/compare/v0.7.0...v0.8.0) --- updated-dependencies: - dependency-name: reqwest-streams dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/indexer-proxy/proxy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index e0920f57d..d0ff3b42a 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -20,7 +20,7 @@ hex = "0.4" jsonwebtoken = "9.1" redis = { version = "0.27", features = ["tokio-comp"] } reqwest = { version = "0.12", features = ["json", "blocking"] } -reqwest-streams = { version = "0.7", features = ["json"] } +reqwest-streams = { version = "0.8", features = ["json"] } once_cell = "1.12" prometheus-client = "0.22" sha2 = '0.10' From 5b37c7f152ea28476c71e99ba253e29dfe68183d Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:28:24 +0800 Subject: [PATCH 10/43] fix lifetime warning (#510) --- apps/indexer-proxy/proxy/src/project.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-proxy/proxy/src/project.rs b/apps/indexer-proxy/proxy/src/project.rs index 8681af8a0..a480dbcc5 100644 --- a/apps/indexer-proxy/proxy/src/project.rs +++ b/apps/indexer-proxy/proxy/src/project.rs @@ -280,7 +280,7 @@ impl Project { } } - pub fn endpoint<'a>(&'a self, ep_name: &str, no_internal: bool) -> Result<&Endpoint> { + pub fn endpoint<'a>(&'a self, ep_name: &str, no_internal: bool) -> Result<&'a Endpoint> { if let Some(end) = self.endpoints.get(ep_name) { if no_internal && end.is_internal { Err(Error::InvalidServiceEndpoint(1037)) From ffc08f23742d6c618c5fac9c50a11eb014692719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:03:49 +0800 Subject: [PATCH 11/43] build(deps): update tower-http requirement from 0.5 to 0.6 (#512) Updates the requirements on [tower-http](https://github.com/tower-rs/tower-http) to permit the latest version. - [Release notes](https://github.com/tower-rs/tower-http/releases) - [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.5.0...tower-http-0.6.0) --- updated-dependencies: - dependency-name: tower-http dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/indexer-proxy/proxy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index d0ff3b42a..9798ed75e 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -34,7 +34,7 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tokenizers = "0.20" tokio = { version = "1", features = ["full"] } -tower-http = { version = "0.5", features = ["cors"] } +tower-http = { version = "0.6", features = ["cors"] } tokio-stream = { version = "0.1" } tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] } futures-util= "0.3.30" From 78a6d3928effa6e13a41defb0077c9d75a6cd88c Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:46:24 +0800 Subject: [PATCH 12/43] add sentry log (#499) add title and msg to sentry message --- .github/workflows/proxy-docker-dev.yml | 4 +- .github/workflows/proxy-docker-prod.yml | 4 +- apps/indexer-proxy/proxy/Cargo.toml | 24 +++---- apps/indexer-proxy/proxy/Dockerfile | 3 + apps/indexer-proxy/proxy/src/main.rs | 76 ++++++++++++++++++---- apps/indexer-proxy/proxy/src/payg.rs | 39 +++++++++-- apps/indexer-proxy/proxy/src/sentry_log.rs | 68 +++++++++++++++++++ apps/indexer-proxy/proxy/src/server.rs | 17 ++++- 8 files changed, 203 insertions(+), 32 deletions(-) create mode 100644 apps/indexer-proxy/proxy/src/sentry_log.rs diff --git a/.github/workflows/proxy-docker-dev.yml b/.github/workflows/proxy-docker-dev.yml index cf9fd6eec..f1ca7b4fb 100644 --- a/.github/workflows/proxy-docker-dev.yml +++ b/.github/workflows/proxy-docker-dev.yml @@ -31,7 +31,7 @@ jobs: sh .github/workflows/scripts/proxyVersion.sh - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: push: true platforms: amd64 @@ -40,6 +40,8 @@ jobs: file: ./apps/indexer-proxy/proxy/Dockerfile tags: subquerynetwork/indexer-proxy-dev:v${{ steps.fetch-version.outputs.VERSION }},subquerynetwork/indexer-proxy-dev:latest build-args: RELEASE_VERSION=${{ steps.fetch-version.outputs.VERSION }} + secrets: | + SECRETS_SENTRY_DSN=${{ secrets.SENTRY_DSN }} - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/proxy-docker-prod.yml b/.github/workflows/proxy-docker-prod.yml index e7bf7a008..5eb925b37 100644 --- a/.github/workflows/proxy-docker-prod.yml +++ b/.github/workflows/proxy-docker-prod.yml @@ -31,7 +31,7 @@ jobs: sh .github/workflows/scripts/proxyVersion.sh - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: push: true platforms: amd64,arm64 @@ -40,6 +40,8 @@ jobs: file: ./apps/indexer-proxy/proxy/Dockerfile tags: subquerynetwork/indexer-proxy:v${{ steps.fetch-version.outputs.VERSION }} build-args: RELEASE_VERSION=${{ steps.fetch-version.outputs.VERSION }} + secrets: | + SECRETS_SENTRY_DSN=${{ secrets.SENTRY_DSN }} - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index 9798ed75e..d25e963c4 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -1,41 +1,43 @@ [package] name = "subql-indexer-proxy" -version = "2.5.1-beta.2" +version = "2.6.0" edition = "2021" [dependencies] -subql-indexer-utils = { version = "2", path = "../utils" } -subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = "v1.3.0" } aes-gcm = "0.10" axum = { version = "0.7", features = ["ws"] } axum-auth = "0.7" axum-streams = { version = "0.19", features = ["text"] } base64 = "0.22" bincode = "1.3" +cached = "0.53.1" chamomile_types = "0.10" chrono = "0.4" digest = '0.10' ethers = { git = "https://github.com/gakonst/ethers-rs.git", tag = "ethers-v2.0.7" } +futures-util = "0.3.30" hex = "0.4" jsonwebtoken = "9.1" +once_cell = "1.12" +prometheus-client = "0.22" redis = { version = "0.27", features = ["tokio-comp"] } reqwest = { version = "0.12", features = ["json", "blocking"] } reqwest-streams = { version = "0.8", features = ["json"] } -once_cell = "1.12" -prometheus-client = "0.22" -sha2 = '0.10' +sentry = "0.34.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_with={ version = "3.0", features = ["json"] } +serde_with ={ version = "3.0", features = ["json"] } +sha2 = '0.10' structopt = "0.3" +subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = "v1.3.0" } +subql-indexer-utils = { version = "2", path = "../utils" } sysinfo = "0.31" tdn = { version = "0.10", default-features = false, features = ["multiple"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } tokenizers = "0.20" tokio = { version = "1", features = ["full"] } -tower-http = { version = "0.6", features = ["cors"] } tokio-stream = { version = "0.1" } tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] } -futures-util= "0.3.30" +tower-http = { version = "0.6", features = ["cors"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } url = "2.2" diff --git a/apps/indexer-proxy/proxy/Dockerfile b/apps/indexer-proxy/proxy/Dockerfile index 9ac4e5eae..9ccca4629 100644 --- a/apps/indexer-proxy/proxy/Dockerfile +++ b/apps/indexer-proxy/proxy/Dockerfile @@ -3,6 +3,9 @@ FROM rust:buster AS builder RUN update-ca-certificates ENV CARGO_NET_GIT_FETCH_WITH_CLI=true +ARG SECRETS_SENTRY_DSN +ENV SECRETS_SENTRY_DSN=${SECRETS_SENTRY_DSN} + WORKDIR /subql COPY . . diff --git a/apps/indexer-proxy/proxy/src/main.rs b/apps/indexer-proxy/proxy/src/main.rs index 5741c6ac6..f54fcd22c 100644 --- a/apps/indexer-proxy/proxy/src/main.rs +++ b/apps/indexer-proxy/proxy/src/main.rs @@ -34,29 +34,79 @@ mod payg; mod primitives; mod project; mod response; +mod sentry_log; mod server; mod subscriber; mod websocket; mod whitelist; use cli::COMMAND; +use sentry_log::before_send; +use std::sync::Arc; use tracing::Level; -#[tokio::main] -async fn main() { - let port = COMMAND.port(); - let debug = COMMAND.debug(); +const GITHUB_SENTRY_DSN: Option<&'static str> = option_env!("SECRETS_SENTRY_DSN"); - let log_filter = if debug { Level::DEBUG } else { Level::WARN }; - tracing_subscriber::fmt().with_max_level(log_filter).init(); +fn main() { + if let Some(sentry_dsn) = GITHUB_SENTRY_DSN { + let sentry_option = sentry::ClientOptions { + before_send: Some(Arc::new(Box::new(before_send))), + release: sentry::release_name!(), + debug: true, + auto_session_tracking: true, + attach_stacktrace: true, + ..Default::default() + }; - cli::init_redis().await; + let _sentry = sentry::init((sentry_dsn, sentry_option)); - subscriber::subscribe(); - monitor::listen(); - p2p::listen(); - metrics::listen(); - whitelist::listen(); + start_tokio_main(); + } else { + start_tokio_main(); + } +} + +fn start_tokio_main() { + let body = async { + let port = COMMAND.port(); + let debug = COMMAND.debug(); + + let log_filter = if debug { Level::DEBUG } else { Level::WARN }; + tracing_subscriber::fmt().with_max_level(log_filter).init(); + + cli::init_redis().await; - server::start_server(port).await; + subscriber::subscribe(); + monitor::listen(); + p2p::listen(); + metrics::listen(); + whitelist::listen(); + // tokio::spawn(test_sentry()); + + server::start_server(port).await; + }; + + #[allow(clippy::expect_used, clippy::diverging_sub_expression)] + { + return tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("Failed building the Runtime") + .block_on(body); + } } + +// async fn test_sentry() { +// loop { +// tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; +// let sentry_msg = format!( +// "ep_query_handler, not inline or wrapped, ep_name: {} ||| sabc", +// "test_end_point" +// ); +// sentry::capture_message(&sentry_msg, sentry::Level::Error); + +// tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; +// let maybe_number: Result = Err("This will crash"); +// let _number = maybe_number.unwrap(); // This will panic +// } +// } diff --git a/apps/indexer-proxy/proxy/src/payg.rs b/apps/indexer-proxy/proxy/src/payg.rs index c3682b4d2..4311262c0 100644 --- a/apps/indexer-proxy/proxy/src/payg.rs +++ b/apps/indexer-proxy/proxy/src/payg.rs @@ -52,9 +52,10 @@ use crate::contracts::{ use crate::metrics::{MetricsNetwork, MetricsQuery}; use crate::p2p::report_conflict; use crate::project::{get_project, list_projects, Project}; - +use crate::sentry_log::make_sentry_message; const CURRENT_VERSION: u8 = 3; +#[derive(Debug)] pub struct StateCache { pub expiration: i64, pub agent: Address, @@ -142,6 +143,7 @@ impl StateCache { } /// Supported consumer type. +#[derive(Debug)] pub enum ConsumerType { /// real account Account(Vec
), @@ -584,19 +586,48 @@ pub fn check_multiple_state_balance( let remote_prev = state_cache.remote; let used_amount = price * unit_times; - let local_next = state_cache.spent + used_amount; + let (local_next, flag) = state_cache.spent.overflowing_add(used_amount); + if flag { + let sentry_msg = make_sentry_message("overflowing_add used_amount overflow", + &format!("state_cache is {:#?}, used_amount is {:#?}, state_cache: {:#?}, unit_times: {}, start: {:#?}, end: {:#?}", state_cache, used_amount, state_cache, + unit_times, + start, + end)); + + sentry::capture_message(&sentry_msg, sentry::Level::Error); + return Err(Error::Overflow(1058)); + } if local_next > total { // overflow the total return Err(Error::Overflow(1056)); } - let range = end - start; + let (range, flag) = end.overflowing_sub(start); + if flag { + let sentry_msg = make_sentry_message("overflowing_sub start overflow", + &format!("start is {:#?}, end is {:#?}, state_cache: {:#?}, unit_times: {}, start: {:#?}, end: {:#?}", start, end, state_cache, + unit_times, + start, + end)); + sentry::capture_message(&sentry_msg, sentry::Level::Error); + return Err(Error::Overflow(1058)); + } + if range > MULTIPLE_RANGE_MAX { return Err(Error::Overflow(1059)); } - let middle = start + range / 2; + let (middle, flag) = start.overflowing_add(range / 2); + if flag { + let sentry_msg = make_sentry_message("overflowing_add range overflow", + &format!("start is {:#?}, range is {:#?}, state_cache: {:#?}, unit_times: {}, start: {:#?}, end: {:#?}", start, range, state_cache, + unit_times, + start, + end)); + sentry::capture_message(&sentry_msg, sentry::Level::Error); + return Err(Error::Overflow(1058)); + } let mut mpqsa = if local_next < middle { MultipleQueryStateActive::Active } else if local_next > end { diff --git a/apps/indexer-proxy/proxy/src/sentry_log.rs b/apps/indexer-proxy/proxy/src/sentry_log.rs new file mode 100644 index 000000000..d5dd4f1d8 --- /dev/null +++ b/apps/indexer-proxy/proxy/src/sentry_log.rs @@ -0,0 +1,68 @@ +// This file is part of SubQuery. + +// Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::COMMAND; +use cached::{stores::TimedSizedCache, Cached}; +use once_cell::sync::Lazy; +use sentry::{protocol::Event, types::protocol::v7::Exception}; +use std::sync::Mutex; + +static GLOBAL_MSG_SET: Lazy>> = + Lazy::new(|| Mutex::new(TimedSizedCache::with_size_and_lifespan(1000, 600))); +static GLOBAL_HASH_SET: Lazy>> = + Lazy::new(|| Mutex::new(TimedSizedCache::with_size_and_lifespan(1000, 600))); + +pub fn make_sentry_message(unique_title: &str, msg: &str) -> String { + format!("{} ||| {}", unique_title, msg) +} + +pub fn before_send(mut event: Event<'static>) -> Option> { + if let Some(ref message) = event.message { + let mut msg_set = GLOBAL_MSG_SET.lock().unwrap(); + let first_part = message.split("|||").next().unwrap_or(message).trim(); + if !msg_set.cache_get(first_part).is_some() { + msg_set.cache_set(first_part.to_string(), ()); + drop(msg_set); + add_event_extra_info(&mut event); + return Some(event); + } + } + if !event.exception.values.is_empty() { + if let Some(exception_str) = get_first_value_of_exception(&event.exception.values) { + let mut hash_set = GLOBAL_HASH_SET.lock().unwrap(); + if !hash_set.cache_get(exception_str).is_some() { + hash_set.cache_set(exception_str.clone(), ()); + drop(hash_set); + add_event_extra_info(&mut event); + return Some(event); + } + } + } + None +} + +fn add_event_extra_info(event: &mut Event<'static>) { + let network_name = format!("{:#?}", COMMAND.network()); + event + .extra + .insert("network".to_string(), network_name.into()); +} + +fn get_first_value_of_exception(values: &[Exception]) -> &Option { + &values[0].value +} diff --git a/apps/indexer-proxy/proxy/src/server.rs b/apps/indexer-proxy/proxy/src/server.rs index 67b8ebf46..fb9e81adc 100644 --- a/apps/indexer-proxy/proxy/src/server.rs +++ b/apps/indexer-proxy/proxy/src/server.rs @@ -53,6 +53,7 @@ use crate::payg::{ query_multiple_state, query_single_state, AuthPayg, }; use crate::project::get_project; +use crate::sentry_log::make_sentry_message; use crate::websocket::{connect_to_project_ws, handle_websocket, validate_project, QueryType}; use crate::{ account::{get_indexer, indexer_healthy}, @@ -318,7 +319,7 @@ async fn ep_query_handler( } let (data, signature, limit) = project .check_query( - body, + body.clone(), endpoint.endpoint.clone(), MetricsQuery::CloseAgreement, MetricsNetwork::HTTP, @@ -344,7 +345,19 @@ async fn ep_query_handler( .unwrap_or("".to_owned()), vec![("X-Indexer-Response-Format", "wrapped")], ), - _ => ("".to_owned(), vec![]), + _ => { + let unique_title = format!( + "ep_query_handler, not inline or wrapped, deployment_id: {}, ep_name: {}", + deployment_id, ep_name + ); + let msg = format!( + "res_fmt: {:#?}, headers: {:#?}, body: {}", + res_fmt, headers, body + ); + let sentry_msg = make_sentry_message(&unique_title, &msg); + sentry::capture_message(&sentry_msg, sentry::Level::Error); + ("".to_owned(), vec![]) + } }; headers.push(("Content-Type", "application/json")); headers.push(("Access-Control-Max-Age", "600")); From c071ea1bc78cad89ac0507866ccedbeb9fdfa12e Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Mon, 23 Sep 2024 18:17:58 +0800 Subject: [PATCH 13/43] use build-args option (#514) --- .github/workflows/proxy-docker-dev.yml | 4 ++-- apps/indexer-proxy/proxy/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/proxy-docker-dev.yml b/.github/workflows/proxy-docker-dev.yml index f1ca7b4fb..fbb5fbd2b 100644 --- a/.github/workflows/proxy-docker-dev.yml +++ b/.github/workflows/proxy-docker-dev.yml @@ -39,8 +39,8 @@ jobs: cache-to: type=gha,mode=max file: ./apps/indexer-proxy/proxy/Dockerfile tags: subquerynetwork/indexer-proxy-dev:v${{ steps.fetch-version.outputs.VERSION }},subquerynetwork/indexer-proxy-dev:latest - build-args: RELEASE_VERSION=${{ steps.fetch-version.outputs.VERSION }} - secrets: | + build-args: | + RELEASE_VERSION=${{ steps.fetch-version.outputs.VERSION }} SECRETS_SENTRY_DSN=${{ secrets.SENTRY_DSN }} - name: Image digest diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index d25e963c4..cb8635e33 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subql-indexer-proxy" -version = "2.6.0" +version = "2.6.1" edition = "2021" [dependencies] From 976a0dc82746afacfdd110146f8322f0b0816283 Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:12:41 +0800 Subject: [PATCH 14/43] disable sentry debug msg and print sentry-dsn top 20 character (#515) --- apps/indexer-proxy/proxy/src/main.rs | 45 +++++++++++------ apps/indexer-proxy/proxy/src/server.rs | 68 +++++++++++++++++++------- 2 files changed, 80 insertions(+), 33 deletions(-) diff --git a/apps/indexer-proxy/proxy/src/main.rs b/apps/indexer-proxy/proxy/src/main.rs index f54fcd22c..02ddb382f 100644 --- a/apps/indexer-proxy/proxy/src/main.rs +++ b/apps/indexer-proxy/proxy/src/main.rs @@ -52,7 +52,7 @@ fn main() { let sentry_option = sentry::ClientOptions { before_send: Some(Arc::new(Box::new(before_send))), release: sentry::release_name!(), - debug: true, + debug: false, auto_session_tracking: true, attach_stacktrace: true, ..Default::default() @@ -81,7 +81,8 @@ fn start_tokio_main() { p2p::listen(); metrics::listen(); whitelist::listen(); - // tokio::spawn(test_sentry()); + + tokio::spawn(check_sentry_status()); server::start_server(port).await; }; @@ -96,17 +97,29 @@ fn start_tokio_main() { } } -// async fn test_sentry() { -// loop { -// tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; -// let sentry_msg = format!( -// "ep_query_handler, not inline or wrapped, ep_name: {} ||| sabc", -// "test_end_point" -// ); -// sentry::capture_message(&sentry_msg, sentry::Level::Error); - -// tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; -// let maybe_number: Result = Err("This will crash"); -// let _number = maybe_number.unwrap(); // This will panic -// } -// } +async fn check_sentry_status() { + if let Some(sentry_dsn) = GITHUB_SENTRY_DSN { + if sentry_dsn.len() > 20 { + info!( + "sentry is enabled, sentry_dsn top 20 characters is {}", + &sentry_dsn[0..20] + ); + } else { + info!("sentry is enabled, sentry_dsn is {}", sentry_dsn); + } + // loop { + // tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + // let sentry_msg = format!( + // "ep_query_handler, not inline or wrapped, ep_name: {} ||| sabc", + // "test_end_point" + // ); + // sentry::capture_message(&sentry_msg, sentry::Level::Error); + + // tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + // let maybe_number: Result = Err("This will crash"); + // let _number = maybe_number.unwrap(); // This will panic + // } + } else { + info!("sentry is disabled"); + } +} diff --git a/apps/indexer-proxy/proxy/src/server.rs b/apps/indexer-proxy/proxy/src/server.rs index fb9e81adc..5037ef2ce 100644 --- a/apps/indexer-proxy/proxy/src/server.rs +++ b/apps/indexer-proxy/proxy/src/server.rs @@ -330,13 +330,30 @@ async fn ep_query_handler( .await?; let (body, mut headers) = match res_fmt.to_str() { - Ok("inline") => ( - String::from_utf8(data).unwrap_or("".to_owned()), - vec![ - ("X-Indexer-Sig", signature.as_str()), - ("X-Indexer-Response-Format", "inline"), - ], - ), + Ok("inline") => { + let return_body = if let Ok(return_data) = String::from_utf8(data.clone()) { + return_data + } else { + let unique_title = format!( + "ep_query_handler, inline returns empty, deployment_id: {}, ep_name: {}", + deployment_id, ep_name + ); + let msg = format!( + "res_fmt: {:#?}, headers: {:#?}, body: {}, data: {:?}", + res_fmt, headers, body, data + ); + let sentry_msg = make_sentry_message(&unique_title, &msg); + sentry::capture_message(&sentry_msg, sentry::Level::Error); + "".to_owned() + }; + ( + return_body, + vec![ + ("X-Indexer-Sig", signature.as_str()), + ("X-Indexer-Response-Format", "inline"), + ], + ) + } Ok("wrapped") => ( serde_json::to_string(&json!({ "result": general_purpose::STANDARD.encode(&data), @@ -477,7 +494,7 @@ async fn ep_payg_handler( }; match query_multiple_state( &deployment, - body, + body.clone(), endpoint.endpoint.clone(), state, MetricsNetwork::HTTP, @@ -496,7 +513,7 @@ async fn ep_payg_handler( }; match query_single_state( &deployment, - body, + body.clone(), endpoint.endpoint.clone(), state, MetricsNetwork::HTTP, @@ -511,14 +528,31 @@ async fn ep_payg_handler( }; let (body, mut headers) = match res_fmt.to_str() { - Ok("inline") => ( - String::from_utf8(data).unwrap_or("".to_owned()), - vec![ - ("X-Indexer-Sig", signature.as_str()), - ("X-Channel-State", state_data.as_str()), - ("X-Indexer-Response-Format", "inline"), - ], - ), + Ok("inline") => { + let return_body = if let Ok(return_data) = String::from_utf8(data.clone()) { + return_data + } else { + let unique_title = format!( + "payg ep_query_handler, inline returns empty, deployment_id: {}, ep_name: {}", + deployment, ep_name + ); + let msg = format!( + "res_fmt: {:#?}, headers: {:#?}, body: {}, data: {:?}", + res_fmt, headers, body, data + ); + let sentry_msg = make_sentry_message(&unique_title, &msg); + sentry::capture_message(&sentry_msg, sentry::Level::Error); + "".to_owned() + }; + ( + return_body, + vec![ + ("X-Indexer-Sig", signature.as_str()), + ("X-Channel-State", state_data.as_str()), + ("X-Indexer-Response-Format", "inline"), + ], + ) + } // `wrapped` or other res format _ => ( serde_json::to_string(&json!({ From 3419d6247408c5d1b9585aa6f0a54cf6a1ffceaf Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Tue, 24 Sep 2024 20:54:58 +0800 Subject: [PATCH 15/43] check sentry is enabled (#516) --- apps/indexer-proxy/proxy/src/main.rs | 2 +- apps/indexer-proxy/proxy/src/payg.rs | 34 ++++++++++------------ apps/indexer-proxy/proxy/src/sentry_log.rs | 8 +++-- apps/indexer-proxy/proxy/src/server.rs | 9 ++---- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/apps/indexer-proxy/proxy/src/main.rs b/apps/indexer-proxy/proxy/src/main.rs index 02ddb382f..f9e530af2 100644 --- a/apps/indexer-proxy/proxy/src/main.rs +++ b/apps/indexer-proxy/proxy/src/main.rs @@ -45,7 +45,7 @@ use sentry_log::before_send; use std::sync::Arc; use tracing::Level; -const GITHUB_SENTRY_DSN: Option<&'static str> = option_env!("SECRETS_SENTRY_DSN"); +pub const GITHUB_SENTRY_DSN: Option<&'static str> = option_env!("SECRETS_SENTRY_DSN"); fn main() { if let Some(sentry_dsn) = GITHUB_SENTRY_DSN { diff --git a/apps/indexer-proxy/proxy/src/payg.rs b/apps/indexer-proxy/proxy/src/payg.rs index 4311262c0..f7314d6a3 100644 --- a/apps/indexer-proxy/proxy/src/payg.rs +++ b/apps/indexer-proxy/proxy/src/payg.rs @@ -588,13 +588,11 @@ pub fn check_multiple_state_balance( let (local_next, flag) = state_cache.spent.overflowing_add(used_amount); if flag { - let sentry_msg = make_sentry_message("overflowing_add used_amount overflow", - &format!("state_cache is {:#?}, used_amount is {:#?}, state_cache: {:#?}, unit_times: {}, start: {:#?}, end: {:#?}", state_cache, used_amount, state_cache, - unit_times, - start, - end)); - - sentry::capture_message(&sentry_msg, sentry::Level::Error); + make_sentry_message("overflowing_add used_amount overflow", + &format!("state_cache is {:#?}, used_amount is {:#?}, state_cache: {:#?}, unit_times: {}, start: {:#?}, end: {:#?}", state_cache, used_amount, state_cache, + unit_times, + start, + end)); return Err(Error::Overflow(1058)); } @@ -605,12 +603,11 @@ pub fn check_multiple_state_balance( let (range, flag) = end.overflowing_sub(start); if flag { - let sentry_msg = make_sentry_message("overflowing_sub start overflow", - &format!("start is {:#?}, end is {:#?}, state_cache: {:#?}, unit_times: {}, start: {:#?}, end: {:#?}", start, end, state_cache, - unit_times, - start, - end)); - sentry::capture_message(&sentry_msg, sentry::Level::Error); + make_sentry_message("overflowing_sub start overflow", + &format!("start is {:#?}, end is {:#?}, state_cache: {:#?}, unit_times: {}, start: {:#?}, end: {:#?}", start, end, state_cache, + unit_times, + start, + end)); return Err(Error::Overflow(1058)); } @@ -620,12 +617,11 @@ pub fn check_multiple_state_balance( let (middle, flag) = start.overflowing_add(range / 2); if flag { - let sentry_msg = make_sentry_message("overflowing_add range overflow", - &format!("start is {:#?}, range is {:#?}, state_cache: {:#?}, unit_times: {}, start: {:#?}, end: {:#?}", start, range, state_cache, - unit_times, - start, - end)); - sentry::capture_message(&sentry_msg, sentry::Level::Error); + make_sentry_message("overflowing_add range overflow", + &format!("start is {:#?}, range is {:#?}, state_cache: {:#?}, unit_times: {}, start: {:#?}, end: {:#?}", start, range, state_cache, + unit_times, + start, + end)); return Err(Error::Overflow(1058)); } let mut mpqsa = if local_next < middle { diff --git a/apps/indexer-proxy/proxy/src/sentry_log.rs b/apps/indexer-proxy/proxy/src/sentry_log.rs index d5dd4f1d8..fbcc0cc51 100644 --- a/apps/indexer-proxy/proxy/src/sentry_log.rs +++ b/apps/indexer-proxy/proxy/src/sentry_log.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::COMMAND; +use crate::GITHUB_SENTRY_DSN; use cached::{stores::TimedSizedCache, Cached}; use once_cell::sync::Lazy; use sentry::{protocol::Event, types::protocol::v7::Exception}; @@ -27,8 +28,11 @@ static GLOBAL_MSG_SET: Lazy>> = static GLOBAL_HASH_SET: Lazy>> = Lazy::new(|| Mutex::new(TimedSizedCache::with_size_and_lifespan(1000, 600))); -pub fn make_sentry_message(unique_title: &str, msg: &str) -> String { - format!("{} ||| {}", unique_title, msg) +pub fn make_sentry_message(unique_title: &str, msg: &str) { + if GITHUB_SENTRY_DSN.is_some() { + let sentry_msg = format!("{} ||| {}", unique_title, msg); + sentry::capture_message(&sentry_msg, sentry::Level::Error); + } } pub fn before_send(mut event: Event<'static>) -> Option> { diff --git a/apps/indexer-proxy/proxy/src/server.rs b/apps/indexer-proxy/proxy/src/server.rs index 5037ef2ce..674fefc1d 100644 --- a/apps/indexer-proxy/proxy/src/server.rs +++ b/apps/indexer-proxy/proxy/src/server.rs @@ -342,8 +342,7 @@ async fn ep_query_handler( "res_fmt: {:#?}, headers: {:#?}, body: {}, data: {:?}", res_fmt, headers, body, data ); - let sentry_msg = make_sentry_message(&unique_title, &msg); - sentry::capture_message(&sentry_msg, sentry::Level::Error); + make_sentry_message(&unique_title, &msg); "".to_owned() }; ( @@ -371,8 +370,7 @@ async fn ep_query_handler( "res_fmt: {:#?}, headers: {:#?}, body: {}", res_fmt, headers, body ); - let sentry_msg = make_sentry_message(&unique_title, &msg); - sentry::capture_message(&sentry_msg, sentry::Level::Error); + make_sentry_message(&unique_title, &msg); ("".to_owned(), vec![]) } }; @@ -540,8 +538,7 @@ async fn ep_payg_handler( "res_fmt: {:#?}, headers: {:#?}, body: {}, data: {:?}", res_fmt, headers, body, data ); - let sentry_msg = make_sentry_message(&unique_title, &msg); - sentry::capture_message(&sentry_msg, sentry::Level::Error); + make_sentry_message(&unique_title, &msg); "".to_owned() }; ( From a99e1d3f6b36dadddcf2eebdb98a6ffec1be40a4 Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:27:31 +0800 Subject: [PATCH 16/43] subgraph is not rpc project (#517) * subgraph is not rpc project * proxy 2.6.2 --- apps/indexer-proxy/proxy/Cargo.toml | 2 +- apps/indexer-proxy/proxy/src/project.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index cb8635e33..2531b3c4e 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subql-indexer-proxy" -version = "2.6.1" +version = "2.6.2" edition = "2021" [dependencies] diff --git a/apps/indexer-proxy/proxy/src/project.rs b/apps/indexer-proxy/proxy/src/project.rs index a480dbcc5..da68ec850 100644 --- a/apps/indexer-proxy/proxy/src/project.rs +++ b/apps/indexer-proxy/proxy/src/project.rs @@ -295,7 +295,7 @@ impl Project { pub fn is_rpc_project(&self) -> bool { matches!( self.ptype, - ProjectType::RpcEvm(_) | ProjectType::RpcSubstrate(_) | ProjectType::Subgraph + ProjectType::RpcEvm(_) | ProjectType::RpcSubstrate(_) ) } From a22b396b156f4a9455e74eefa8a4f4c2ffbd34b8 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Wed, 25 Sep 2024 16:57:49 +0800 Subject: [PATCH 17/43] feat: refactor --- .../src/project/project.rpc.service.ts | 24 +++-- .../src/project/rpc.factory.ts | 101 ++++++++++++------ apps/indexer-coordinator/src/project/types.ts | 31 +++--- 3 files changed, 103 insertions(+), 53 deletions(-) diff --git a/apps/indexer-coordinator/src/project/project.rpc.service.ts b/apps/indexer-coordinator/src/project/project.rpc.service.ts index 2394c1319..942fe38aa 100644 --- a/apps/indexer-coordinator/src/project/project.rpc.service.ts +++ b/apps/indexer-coordinator/src/project/project.rpc.service.ts @@ -19,7 +19,13 @@ import { } from './project.model'; import { ProjectService } from './project.service'; import { RequiredRpcType, getRpcFamilyObject } from './rpc.factory'; -import { AccessType, ENDPOINT_KEY, ErrorLevel, ProjectType } from './types'; +import { + AccessType, + RpcEndpointType, + ErrorLevel, + ProjectType, + RpcEndpointAccessType, +} from './types'; const logger = getLogger('project.rpc.service'); @@ -30,7 +36,7 @@ export class ProjectRpcService { private projectService: ProjectService ) {} - @Cron('0 */8 * * * *') + // @Cron('0 */8 * * * *') async autoValidateRpcEndpoints() { const projects = (await this.projectService.getAliveProjects()).filter( (project) => project.projectType === ProjectType.RPC @@ -75,6 +81,7 @@ export class ProjectRpcService { endpoint.valid = false; endpoint.reason = validateUrlResult.reason; reason = reason || validateUrlResult.reason; + errorLevel = errorLevel || (validateUrlResult.level as ErrorLevel); continue; } const response = await this.validateRpcEndpoint(project.id, endpoint.key, endpoint.value); @@ -87,9 +94,12 @@ export class ProjectRpcService { endpoint.valid = response.valid; endpoint.reason = response.reason; - if (response.level === ErrorLevel.error) { - reason = reason || response.reason; - errorLevel = errorLevel || response.level; + if (response.level !== ErrorLevel.none) { + if (errorLevel === ErrorLevel.error) { + continue; + } + errorLevel = response.level as ErrorLevel; + reason = response.reason; } } return this.formatResponse(!reason, reason, errorLevel); @@ -224,7 +234,7 @@ export class ProjectRpcService { .withNodeType(projectManifest.nodeType) .withHeight() .withClientNameAndVersion(projectManifest.client?.name, projectManifest.client?.version) - .validate(endpoint, endpointKey as ENDPOINT_KEY); + .validate(endpoint, endpointKey as RpcEndpointType); return this.formatResponse(true); } catch (e) { logger.debug(e); @@ -267,7 +277,7 @@ export class ProjectRpcService { } for (const endpoint of project.serviceEndpoints) { - endpoint.access = AccessType.DEFAULT; + endpoint.access = RpcEndpointAccessType[endpoint.key] || AccessType.DEFAULT; endpoint.isWebsocket = endpoint.key.endsWith('Ws'); endpoint.rpcFamily = manifest.rpcFamily || []; } diff --git a/apps/indexer-coordinator/src/project/rpc.factory.ts b/apps/indexer-coordinator/src/project/rpc.factory.ts index 4e58bfc27..c96fb0388 100644 --- a/apps/indexer-coordinator/src/project/rpc.factory.ts +++ b/apps/indexer-coordinator/src/project/rpc.factory.ts @@ -8,7 +8,7 @@ import * as semver from 'semver'; import { safeJSONParse } from 'src/utils/json'; import { getLogger } from 'src/utils/logger'; import { WebSocket } from 'ws'; -import { ENDPOINT_KEY, ErrorLevel, ValidateRpcEndpointError } from './types'; +import { RpcEndpointType, ErrorLevel, ValidateRpcEndpointError } from './types'; const logger = getLogger('rpc.factory'); @@ -100,15 +100,37 @@ async function jsonWsRpcRequest(endpoint: string, method: string, params: any[]) } } -async function jsonMetricsHttpRpcRequest(endpoint: string) { +async function jsonMetricsHttpRpcRequest(endpoint: string): Promise<{ error: string; data: any }> { if (!endpoint) { - throw new Error('Endpoint is empty'); + return { + error: 'Endpoint is empty', + data: null, + }; } - return axios.request({ - url: endpoint, - method: 'get', - timeout: 1000 * 10, - }); + try { + const res = await axios.request({ + url: endpoint, + method: 'get', + timeout: 1000 * 10, + }); + return { + error: '', + data: res.data, + }; + } catch (err) { + return { + error: err.message, + data: null, + }; + } + // if (!endpoint) { + // throw new Error('Endpoint is empty'); + // } + // return axios.request({ + // url: endpoint, + // method: 'get', + // timeout: 1000 * 10, + // }); } function getRpcRequestFunction(endpoint: string) { @@ -133,7 +155,7 @@ export interface IRpcFamily { withClientNameAndVersion(clientName: string, clientVersion: string): IRpcFamily; withClientVersion(clientVersion: string): IRpcFamily; withHeight(height?: number): IRpcFamily; - validate(endpoint: string, endpointKey: ENDPOINT_KEY): Promise; + validate(endpoint: string, endpointKey: string): Promise; getStartHeight(endpoint: string): Promise; getTargetHeight(endpoint: string): Promise; getLastHeight(endpoint: string): Promise; @@ -144,9 +166,9 @@ abstract class RpcFamily implements IRpcFamily { protected actions: (() => Promise)[] = []; protected endpoint: string; protected requiredRpcType: RequiredRpcType = RequiredRpcType.http; - protected targetEndpointKey: ENDPOINT_KEY; + protected targetEndpointKey: RpcEndpointType; - async validate(endpoint: string, endpointKey: ENDPOINT_KEY) { + async validate(endpoint: string, endpointKey: RpcEndpointType) { this.endpoint = endpoint; this.targetEndpointKey = endpointKey; @@ -196,7 +218,7 @@ abstract class RpcFamily implements IRpcFamily { export class RpcFamilyEvm extends RpcFamily { getEndpointKeys(): string[] { - return [ENDPOINT_KEY.evmWs, ENDPOINT_KEY.evmHttp, ENDPOINT_KEY.evmMetricsHttp]; + return [RpcEndpointType.evmWs, RpcEndpointType.evmHttp, RpcEndpointType.evmMetricsHttp]; } withChainId(chainId: string): IRpcFamily { @@ -204,13 +226,13 @@ export class RpcFamilyEvm extends RpcFamily { let p = null; let errorLevel = ErrorLevel.error; switch (this.targetEndpointKey) { - case ENDPOINT_KEY.evmHttp: + case RpcEndpointType.evmHttp: p = jsonRpcRequest(this.endpoint, 'eth_chainId', []); break; - case ENDPOINT_KEY.evmWs: + case RpcEndpointType.evmWs: p = jsonWsRpcRequest(this.endpoint, 'eth_chainId', []); break; - case ENDPOINT_KEY.evmMetricsHttp: + case RpcEndpointType.evmMetricsHttp: p = jsonMetricsHttpRpcRequest(this.endpoint); errorLevel = ErrorLevel.warn; break; @@ -218,16 +240,25 @@ export class RpcFamilyEvm extends RpcFamily { throw new ValidateRpcEndpointError('Invalid endpointKey', errorLevel); } const result = await p; - if (result.data.error) { - throw new ValidateRpcEndpointError( - `Request eth_chainId failed: ${result.data.error.message}`, - errorLevel - ); - } - let chainIdFromRpc = result.data.result; - if (this.targetEndpointKey === ENDPOINT_KEY.evmMetricsHttp) { + let chainIdFromRpc = null; + + if (this.targetEndpointKey === RpcEndpointType.evmMetricsHttp) { + if (result.error) { + throw new ValidateRpcEndpointError( + `Request eth_chainId failed: ${result.error}`, + errorLevel + ); + } const info = safeJSONParse(result.data['chain/info']); chainIdFromRpc = info?.chain_id; + } else { + if (result.data.error) { + throw new ValidateRpcEndpointError( + `Request eth_chainId failed: ${result.data.error.message}`, + errorLevel + ); + } + chainIdFromRpc = result.data.result; } if (!BigNumber.from(chainIdFromRpc).eq(BigNumber.from(chainId || 0))) { @@ -244,7 +275,7 @@ export class RpcFamilyEvm extends RpcFamily { withGenesisHash(genesisHash: string): IRpcFamily { this.actions.push(async () => { - if (this.targetEndpointKey === ENDPOINT_KEY.evmMetricsHttp) { + if (this.targetEndpointKey === RpcEndpointType.evmMetricsHttp) { return; } @@ -269,7 +300,7 @@ export class RpcFamilyEvm extends RpcFamily { withNodeType(nodeType: string): IRpcFamily { this.actions.push(async () => { - if (this.targetEndpointKey === ENDPOINT_KEY.evmMetricsHttp) { + if (this.targetEndpointKey === RpcEndpointType.evmMetricsHttp) { return; } const result = await getRpcRequestFunction(this.endpoint)(this.endpoint, 'eth_getBalance', [ @@ -288,7 +319,7 @@ export class RpcFamilyEvm extends RpcFamily { if (!clientName && !clientVersion) { return; } - if (this.targetEndpointKey === ENDPOINT_KEY.evmMetricsHttp) { + if (this.targetEndpointKey === RpcEndpointType.evmMetricsHttp) { return; } const result = await getRpcRequestFunction(this.endpoint)( @@ -381,7 +412,7 @@ export class RpcFamilySubstrate extends RpcFamily { } getEndpointKeys(): string[] { - return [ENDPOINT_KEY.substrateWs, ENDPOINT_KEY.substrateHttp]; + return [RpcEndpointType.substrateWs, RpcEndpointType.substrateHttp]; } withChainId(chainId: string): IRpcFamily { @@ -496,12 +527,16 @@ export class RpcFamilySubstrate extends RpcFamily { export class RpcFamilyPolkadot extends RpcFamilySubstrate { getEndpointKeys(): string[] { - return [ENDPOINT_KEY.polkadotWs, ENDPOINT_KEY.polkadotHttp, ENDPOINT_KEY.polkadotMetricsHttp]; + return [ + RpcEndpointType.polkadotWs, + RpcEndpointType.polkadotHttp, + RpcEndpointType.polkadotMetricsHttp, + ]; } withGenesisHash(genesisHash: string): IRpcFamily { this.actions.push(async () => { - if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { + if (this.targetEndpointKey === RpcEndpointType.polkadotMetricsHttp) { return this; } return super.withGenesisHash(genesisHash); @@ -511,7 +546,7 @@ export class RpcFamilyPolkadot extends RpcFamilySubstrate { withNodeType(nodeType: string): IRpcFamily { this.actions.push(async () => { - if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { + if (this.targetEndpointKey === RpcEndpointType.polkadotMetricsHttp) { return this; } return super.withNodeType(nodeType); @@ -521,11 +556,11 @@ export class RpcFamilyPolkadot extends RpcFamilySubstrate { withHeight(height?: number): IRpcFamily { this.actions.push(async () => { - if (this.targetEndpointKey === ENDPOINT_KEY.polkadotMetricsHttp) { + if (this.targetEndpointKey === RpcEndpointType.polkadotMetricsHttp) { const result = await jsonMetricsHttpRpcRequest(this.endpoint); - if (result.data.error) { + if (result.error) { throw new ValidateRpcEndpointError( - `Request metrics failed: ${result.data.error.message}`, + `Request metrics failed: ${result.error}`, ErrorLevel.warn ); } diff --git a/apps/indexer-coordinator/src/project/types.ts b/apps/indexer-coordinator/src/project/types.ts index 17ecc5653..bae19b827 100644 --- a/apps/indexer-coordinator/src/project/types.ts +++ b/apps/indexer-coordinator/src/project/types.ts @@ -68,6 +68,24 @@ export const SubgraphEndpointAccessType = { [SubgraphEndpointType.MetricsEndpoint]: AccessType.INTERNAL, }; +export enum RpcEndpointType { + evmHttp = 'evmHttp', + evmWs = 'evmWs', + evmMetricsHttp = 'evmMetricsHttp', + + polkadotWs = 'polkadotWs', + polkadotHttp = 'polkadotHttp', + polkadotMetricsHttp = 'polkadotMetricsHttp', + + substrateWs = 'substrateWs', + substrateHttp = 'substrateHttp', +} + +export const RpcEndpointAccessType = { + [RpcEndpointType.evmMetricsHttp]: AccessType.INTERNAL, + [RpcEndpointType.polkadotMetricsHttp]: AccessType.INTERNAL, +}; + @InputType('SubgraphPort') @ObjectType('SubgraphPort') export class SubgraphPort { @@ -127,19 +145,6 @@ export type TemplateType = { pgCert?: string; }; -export enum ENDPOINT_KEY { - evmHttp = 'evmHttp', - evmWs = 'evmWs', - evmMetricsHttp = 'evmMetricsHttp', - - polkadotWs = 'polkadotWs', - polkadotHttp = 'polkadotHttp', - polkadotMetricsHttp = 'polkadotMetricsHttp', - - substrateWs = 'substrateWs', - substrateHttp = 'substrateHttp', -} - export enum ErrorLevel { none = '', warn = 'warn', From 754ab1a30aced7f063840d7d2c73f38ca641b99f Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Thu, 26 Sep 2024 17:14:40 +0800 Subject: [PATCH 18/43] feat: metrics --- .../project-details/components/rpcSetting.tsx | 265 +++++++++++++----- apps/indexer-admin/src/utils/queries.ts | 1 + 2 files changed, 197 insertions(+), 69 deletions(-) diff --git a/apps/indexer-admin/src/pages/project-details/components/rpcSetting.tsx b/apps/indexer-admin/src/pages/project-details/components/rpcSetting.tsx index cc7766a60..d331c43d4 100644 --- a/apps/indexer-admin/src/pages/project-details/components/rpcSetting.tsx +++ b/apps/indexer-admin/src/pages/project-details/components/rpcSetting.tsx @@ -6,6 +6,7 @@ import { useParams } from 'react-router'; import { useLazyQuery, useMutation, useQuery } from '@apollo/client'; import { Spinner, Steps, Typography } from '@subql/components'; import { cidToBytes32 } from '@subql/network-clients'; +import { useUpdate } from 'ahooks'; import { Button, Form, Input, InputNumber } from 'antd'; import { Rule } from 'antd/es/form'; import debounce from 'debounce-promise'; @@ -23,6 +24,20 @@ interface IProps { id?: string; } +const getRuleField = (key: string) => { + const lowerCaseKey = key.toLowerCase(); + + if (lowerCaseKey.includes('metrics')) { + return 'metrics'; + } + + if (lowerCaseKey.includes('http')) { + return 'http'; + } + + return 'ws'; +}; + const RpcSetting: FC = (props) => { const { onCancel, onSubmit, id: propsId } = props; @@ -30,6 +45,11 @@ const RpcSetting: FC = (props) => { const mineId = useMemo(() => propsId || id, [propsId, id]); const projectQuery = useProjectDetails(mineId); const [form] = Form.useForm(); + const update = useUpdate(); + const [loadingUpdate, setLoadingUpdate] = React.useState(false); + const inputFieldFeedback = React.useRef<{ + [key in string]: 'warn' | 'error' | 'success' | 'validating' | ''; + }>({}); const keys = useQuery<{ getRpcEndpointKeys: string[] }>(GET_RPC_ENDPOINT_KEYS, { variables: { @@ -39,15 +59,30 @@ const RpcSetting: FC = (props) => { const rules = useMemo(() => { const checkIfWsAndHttpSame = () => { - const allValues = keys.data?.getRpcEndpointKeys.map((key) => { - try { - return new URL(form.getFieldValue(key)).hostname; - } catch { - return form.getFieldValue(key); - } - }); + const allValues = keys.data?.getRpcEndpointKeys + .map((key) => { + try { + return { + key, + val: new URL(form.getFieldValue(key)).hostname, + }; + } catch { + return { + key, + val: form.getFieldValue(key), + }; + } + }) + .filter((i) => i.val); - const ifSame = new Set(allValues).size === 1; + if ((allValues?.length || 0) < 2) { + return { + result: false, + message: 'Please input endpoint', + }; + } + + const ifSame = new Set(allValues?.map((i) => i.val)).size === 1; if (ifSame) return { @@ -56,15 +91,25 @@ const RpcSetting: FC = (props) => { return { result: false, - message: 'The origin of Ws and Http endpoint should be the same.', + message: 'The origin of all endpoint should be the same.', }; }; const polkadotAndSubstrateRule = ( - endpointType: 'http' | 'ws', + endpointType: 'http' | 'ws' | 'metrics', value: string, ruleField: string ) => { + if (endpointType === 'metrics') { + if (value) { + return checkIfWsAndHttpSame(); + } + + return { + result: true, + }; + } + if (endpointType === 'ws') { if (!value) return { @@ -112,7 +157,19 @@ const RpcSetting: FC = (props) => { }; return { - evm: (endpointType: 'http' | 'ws', value: string, ruleField: string) => { + evm: (endpointType: 'http' | 'ws' | 'metrics', value: string, ruleField: string) => { + if (endpointType === 'metrics') { + const metricsVal = form.getFieldValue(ruleField); + + if (metricsVal) { + return checkIfWsAndHttpSame(); + } + + return { + result: true, + }; + } + if (endpointType === 'ws') { const wsVal = form.getFieldValue(ruleField.replace('Http', 'Ws')); if (wsVal && wsVal?.startsWith('ws')) { @@ -146,7 +203,7 @@ const RpcSetting: FC = (props) => { }, [form, keys.data?.getRpcEndpointKeys]); const [validate] = useLazyQuery< - { validateRpcEndpoint: { valid: boolean; reason?: string } }, + { validateRpcEndpoint: { valid: boolean; reason?: string; level: 'warn' | 'error' | '' } }, { projectId: string; endpointKey: string; endpoint: string } >(VALID_RPC_ENDPOINT); @@ -160,6 +217,13 @@ const RpcSetting: FC = (props) => { // @ts-ignore const ruleField = rule.field as string; const ruleKeys = Object.keys(rules); + + inputFieldFeedback.current = { + ...inputFieldFeedback.current, + [ruleField]: 'validating', + }; + update(); + const whichRule = rules[ ruleKeys.find((key) => ruleField.includes(key as string)) as @@ -167,18 +231,27 @@ const RpcSetting: FC = (props) => { | 'polkadot' | 'substrate' ]; - const { result, message } = whichRule( - ruleField.toLocaleLowerCase().includes('http') ? 'http' : 'ws', - value, - ruleField - ); + + const { result, message } = whichRule(getRuleField(ruleField), value, ruleField); if (!result) { + inputFieldFeedback.current = { + ...inputFieldFeedback.current, + [ruleField]: 'error', + }; + update(); return Promise.reject(new Error(message)); } // whichRule should validate if the field can be empty, if empty, just true - if (!value) return Promise.resolve(); + if (!value) { + inputFieldFeedback.current = { + ...inputFieldFeedback.current, + [ruleField]: 'success', + }; + update(); + return Promise.resolve(); + } const res = await validate({ variables: { @@ -190,10 +263,20 @@ const RpcSetting: FC = (props) => { fetchPolicy: 'network-only', }, }); - + inputFieldFeedback.current = { + ...inputFieldFeedback.current, + [ruleField]: res?.data?.validateRpcEndpoint.level || '', + }; + update(); if (!res?.data?.validateRpcEndpoint.valid) { return Promise.reject(new Error(res?.data?.validateRpcEndpoint.reason)); } + + inputFieldFeedback.current = { + ...inputFieldFeedback.current, + [ruleField]: 'success', + }; + update(); // verification RPC endpoint return Promise.resolve(); }; @@ -204,6 +287,7 @@ const RpcSetting: FC = (props) => { [key]: debounce(validateFunc, 1000), }; }, {}) as Record Promise | void>; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [validate, mineId, rules, keys.data?.getRpcEndpointKeys]); if (!projectQuery.data) return ; @@ -266,21 +350,35 @@ const RpcSetting: FC = (props) => { > {keys.data?.getRpcEndpointKeys.map((key) => { return ( - { - return { - validator: debouncedValidator[key], - }; - }, - ]} - > - - + + { + return { + validator: debouncedValidator[key], + }; + }, + ]} + > + + + ); })} @@ -310,43 +408,62 @@ const RpcSetting: FC = (props) => { shape="round" type="primary" style={{ borderColor: 'var(--sq-blue600)', background: 'var(--sq-blue600)' }} + loading={loadingUpdate} onClick={async () => { - await form.validateFields(); - const serviceEndpoints = keys.data?.getRpcEndpointKeys - .map((key) => { - return { - key, - value: form.getFieldValue(`${key}`)?.trim(), - }; - }) - .filter((i) => i.value); - try { - await startProjectRequest({ - variables: { - rateLimit: form.getFieldValue('rateLimit'), - poiEnabled: false, - queryVersion: '', - nodeVersion: '', - networkDictionary: '', - networkEndpoints: '', - batchSize: 1, - workers: 1, - timeout: 1, - cache: 1, - cpu: 1, - memory: 1, - id: mineId, - projectType: projectQuery.data?.project.projectType, - serviceEndpoints, - }, - }); - onSubmit?.(); - } catch (e) { - parseError(e, { - alert: true, - rawMsg: true, - }); + setLoadingUpdate(true); + try { + await form.validateFields(); + } catch (e: any) { + // ValidateErrorEntity + if (e?.errorFields && Array.isArray(e.errorFields)) { + // eslint-disable-next-line no-restricted-syntax + for (const err of e.errorFields) { + const name = err?.name?.[0] as string; + if (inputFieldFeedback.current[name] !== 'warn') { + return; + } + } + } + } + const serviceEndpoints = keys.data?.getRpcEndpointKeys + .map((key) => { + return { + key, + value: form.getFieldValue(`${key}`)?.trim(), + }; + }) + .filter((i) => i.value); + + try { + await startProjectRequest({ + variables: { + rateLimit: form.getFieldValue('rateLimit'), + poiEnabled: false, + queryVersion: '', + nodeVersion: '', + networkDictionary: '', + networkEndpoints: '', + batchSize: 1, + workers: 1, + timeout: 1, + cache: 1, + cpu: 1, + memory: 1, + id: mineId, + projectType: projectQuery.data?.project.projectType, + serviceEndpoints, + }, + }); + onSubmit?.(); + } catch (e) { + parseError(e, { + alert: true, + rawMsg: true, + }); + } + } finally { + setLoadingUpdate(false); } }} > @@ -374,4 +491,14 @@ export const HorizeFormItem = styled.div` } `; +const WithWarning = styled.div` + .ant-form-item-has-warning { + .ant-row { + .ant-form-item-explain-error { + color: var(--sq-warning); + } + } + } +`; + export default RpcSetting; diff --git a/apps/indexer-admin/src/utils/queries.ts b/apps/indexer-admin/src/utils/queries.ts index 3642e06e0..b8fd6d094 100644 --- a/apps/indexer-admin/src/utils/queries.ts +++ b/apps/indexer-admin/src/utils/queries.ts @@ -389,6 +389,7 @@ export const VALID_RPC_ENDPOINT = gql` validateRpcEndpoint(projectId: $projectId, endpointKey: $endpointKey, endpoint: $endpoint) { valid reason + level } } `; From e34e8b467f8a935138e2b378e806bdbdca0684f1 Mon Sep 17 00:00:00 2001 From: Cyrbuzz Date: Thu, 26 Sep 2024 09:22:30 +0000 Subject: [PATCH 19/43] [SKIP CI] Prerelease --- apps/indexer-coordinator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/package.json b/apps/indexer-coordinator/package.json index ad83b48c1..11ac40332 100644 --- a/apps/indexer-coordinator/package.json +++ b/apps/indexer-coordinator/package.json @@ -1,6 +1,6 @@ { "name": "@subql/indexer-coordinator", - "version": "2.4.1", + "version": "2.4.2-0", "description": "", "author": "SubQuery", "license": "Apache-2.0", From 6c466a2a7a7ae9e6926384c2f55c268d7da96191 Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:14:06 +0800 Subject: [PATCH 20/43] use rust to bullseye (#519) --- apps/indexer-proxy/proxy/Cargo.toml | 2 +- apps/indexer-proxy/proxy/Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index 2531b3c4e..2cfbed00f 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subql-indexer-proxy" -version = "2.6.2" +version = "2.6.3-beta.2" edition = "2021" [dependencies] diff --git a/apps/indexer-proxy/proxy/Dockerfile b/apps/indexer-proxy/proxy/Dockerfile index 9ccca4629..2cd6d2b19 100644 --- a/apps/indexer-proxy/proxy/Dockerfile +++ b/apps/indexer-proxy/proxy/Dockerfile @@ -1,5 +1,5 @@ # Builder -FROM rust:buster AS builder +FROM rust:bullseye AS builder RUN update-ca-certificates ENV CARGO_NET_GIT_FETCH_WITH_CLI=true @@ -18,7 +18,7 @@ RUN --mount=type=cache,id=cargo_bin,target=~/.cargo/bin/ \ cargo update && cargo build --release && mv /subql/target/release/subql-indexer-proxy /subql/ # Final image -FROM debian:buster-slim +FROM debian:bullseye-slim RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && \ apt-get --assume-yes install curl && \ From 60dd65be4fce70697dbea80d7cf8d605a9b19b1e Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Sat, 28 Sep 2024 15:02:29 +0800 Subject: [PATCH 21/43] feat: revert --- apps/indexer-coordinator/src/project/project.rpc.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/src/project/project.rpc.service.ts b/apps/indexer-coordinator/src/project/project.rpc.service.ts index 942fe38aa..83b7c287e 100644 --- a/apps/indexer-coordinator/src/project/project.rpc.service.ts +++ b/apps/indexer-coordinator/src/project/project.rpc.service.ts @@ -36,7 +36,7 @@ export class ProjectRpcService { private projectService: ProjectService ) {} - // @Cron('0 */8 * * * *') + @Cron('0 */8 * * * *') async autoValidateRpcEndpoints() { const projects = (await this.projectService.getAliveProjects()).filter( (project) => project.projectType === ProjectType.RPC From 63e56ee59552f151332a82c0e3631ea35b91e187 Mon Sep 17 00:00:00 2001 From: Ian He <39037239+ianhe8x@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:19:02 +1300 Subject: [PATCH 22/43] trigger reduce allocation more often --- apps/indexer-coordinator/src/reward/reward.service.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/indexer-coordinator/src/reward/reward.service.ts b/apps/indexer-coordinator/src/reward/reward.service.ts index c05af5958..4382dede7 100644 --- a/apps/indexer-coordinator/src/reward/reward.service.ts +++ b/apps/indexer-coordinator/src/reward/reward.service.ts @@ -64,11 +64,15 @@ export class RewardService implements OnModuleInit { @Cron('1 1 1 * * *') async autoRunTasks() { await this.collectAllocationRewards(TxType.check); + await this.collectStateChannelRewards(TxType.check); + } + + @Cron('0 */5 * * * *) + async triggerReduceAllocation() { const reduceEnabled = await this.configService.get(ConfigType.AUTO_REDUCE_ALLOCATION_ENABLED); if (reduceEnabled) { await this.reduceAllocation(TxType.check); } - await this.collectStateChannelRewards(TxType.check); } @Cron('0 */30 * * * *') @@ -76,9 +80,6 @@ export class RewardService implements OnModuleInit { if (this.txOngoingMap[this.collectAllocationRewards.name]) { await this.collectAllocationRewards(TxType.postponed); } - if (this.txOngoingMap[this.reduceAllocation.name]) { - await this.reduceAllocation(TxType.postponed); - } if (this.txOngoingMap[this.collectStateChannelRewards.name]) { await this.collectStateChannelRewards(TxType.postponed); } From 7bf2096255797720d18b0498b52aad9cfa00bb4c Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Tue, 1 Oct 2024 10:47:24 +0800 Subject: [PATCH 23/43] fix: syntax & base test --- apps/indexer-coordinator/src/reward/reward.service.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/indexer-coordinator/src/reward/reward.service.ts b/apps/indexer-coordinator/src/reward/reward.service.ts index 4382dede7..c3d63f4ba 100644 --- a/apps/indexer-coordinator/src/reward/reward.service.ts +++ b/apps/indexer-coordinator/src/reward/reward.service.ts @@ -67,7 +67,7 @@ export class RewardService implements OnModuleInit { await this.collectStateChannelRewards(TxType.check); } - @Cron('0 */5 * * * *) + @Cron('0 */5 * * * *') async triggerReduceAllocation() { const reduceEnabled = await this.configService.get(ConfigType.AUTO_REDUCE_ALLOCATION_ENABLED); if (reduceEnabled) { @@ -143,8 +143,11 @@ export class RewardService implements OnModuleInit { return; } const allocation = await this.onChainService.getRunnerAllocation(indexerId); + if (!allocation) { + this.logger.error('getRunnerAllocation is null'); + return; + } this.txOngoingMap[this.reduceAllocation.name] = false; - const expectTotalReduce = allocation.used.sub(allocation.total); let refetch = true; From 01367748e0c4d67b9d20314cb2f1c106982aed98 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Tue, 1 Oct 2024 03:14:34 +0000 Subject: [PATCH 24/43] [SKIP CI] Prerelease --- apps/indexer-coordinator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/package.json b/apps/indexer-coordinator/package.json index 11ac40332..66d285da2 100644 --- a/apps/indexer-coordinator/package.json +++ b/apps/indexer-coordinator/package.json @@ -1,6 +1,6 @@ { "name": "@subql/indexer-coordinator", - "version": "2.4.2-0", + "version": "2.4.2-1", "description": "", "author": "SubQuery", "license": "Apache-2.0", From 2a9621378b3a20145c9f490f97866ac64b33787a Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Tue, 1 Oct 2024 11:25:11 +0800 Subject: [PATCH 25/43] feat: v2.4.2 --- apps/indexer-coordinator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/package.json b/apps/indexer-coordinator/package.json index 66d285da2..5fcbef4bb 100644 --- a/apps/indexer-coordinator/package.json +++ b/apps/indexer-coordinator/package.json @@ -1,6 +1,6 @@ { "name": "@subql/indexer-coordinator", - "version": "2.4.2-1", + "version": "2.4.2", "description": "", "author": "SubQuery", "license": "Apache-2.0", From cd16ca78e4d9b4d147d5ade4795d5196f6618403 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Tue, 1 Oct 2024 11:51:00 +0800 Subject: [PATCH 26/43] feat: change getRunnerAllocation to debug --- apps/indexer-coordinator/src/reward/reward.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/src/reward/reward.service.ts b/apps/indexer-coordinator/src/reward/reward.service.ts index c3d63f4ba..8fdc018ed 100644 --- a/apps/indexer-coordinator/src/reward/reward.service.ts +++ b/apps/indexer-coordinator/src/reward/reward.service.ts @@ -144,7 +144,7 @@ export class RewardService implements OnModuleInit { } const allocation = await this.onChainService.getRunnerAllocation(indexerId); if (!allocation) { - this.logger.error('getRunnerAllocation is null'); + this.logger.debug('getRunnerAllocation is null'); return; } this.txOngoingMap[this.reduceAllocation.name] = false; From dcc01aa36c3f3c65a911431dfaa997319b7d87cb Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:23:30 +0800 Subject: [PATCH 27/43] update version in deployment (#523) subquerynetwork/indexer-coordinator:v2.4.2 subquerynetwork/indexer-proxy:v2.6.2 --- deploy/docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 39a6f853e..08285f88e 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 coordinator: - image: subquerynetwork/indexer-coordinator:v2.4.1 + image: subquerynetwork/indexer-coordinator:v2.4.2 container_name: indexer_coordinator restart: always ports: @@ -76,7 +76,7 @@ services: test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping'] proxy: - image: subquerynetwork/indexer-proxy:v2.5.0 + image: subquerynetwork/indexer-proxy:v2.6.2 container_name: indexer_proxy restart: always ports: From 82fe90aeb5a4e22cc4af0db1fbda4dc656d21a28 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Mon, 7 Oct 2024 17:07:36 +0800 Subject: [PATCH 28/43] refactor: revert --- .../src/project/rpc.factory.ts | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/apps/indexer-coordinator/src/project/rpc.factory.ts b/apps/indexer-coordinator/src/project/rpc.factory.ts index c96fb0388..a4163f2af 100644 --- a/apps/indexer-coordinator/src/project/rpc.factory.ts +++ b/apps/indexer-coordinator/src/project/rpc.factory.ts @@ -172,11 +172,7 @@ abstract class RpcFamily implements IRpcFamily { this.endpoint = endpoint; this.targetEndpointKey = endpointKey; - while (this.actions.length) { - const action = this.actions.shift(); - await action(); - } - // await Promise.all(this.actions.map((action) => action())); + await Promise.all(this.actions.map((action) => action())); } getEndpointKeys(): string[] { throw new Error('Method not implemented.'); @@ -421,6 +417,9 @@ export class RpcFamilySubstrate extends RpcFamily { withGenesisHash(genesisHash: string): IRpcFamily { this.actions.push(async () => { + if (this.targetEndpointKey === RpcEndpointType.polkadotMetricsHttp) { + return this; + } const result = await getRpcRequestFunction(this.endpoint)( this.endpoint, 'chain_getBlockHash', @@ -442,6 +441,9 @@ export class RpcFamilySubstrate extends RpcFamily { withNodeType(nodeType: string): IRpcFamily { this.actions.push(async () => { + if (this.targetEndpointKey === RpcEndpointType.polkadotMetricsHttp) { + return this; + } if (_.toLower(nodeType) !== 'archive') { return; } @@ -534,26 +536,6 @@ export class RpcFamilyPolkadot extends RpcFamilySubstrate { ]; } - withGenesisHash(genesisHash: string): IRpcFamily { - this.actions.push(async () => { - if (this.targetEndpointKey === RpcEndpointType.polkadotMetricsHttp) { - return this; - } - return super.withGenesisHash(genesisHash); - }); - return this; - } - - withNodeType(nodeType: string): IRpcFamily { - this.actions.push(async () => { - if (this.targetEndpointKey === RpcEndpointType.polkadotMetricsHttp) { - return this; - } - return super.withNodeType(nodeType); - }); - return this; - } - withHeight(height?: number): IRpcFamily { this.actions.push(async () => { if (this.targetEndpointKey === RpcEndpointType.polkadotMetricsHttp) { From c55970758186a72ee80f2ef0e72415a93820d17f Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Mon, 7 Oct 2024 19:09:03 +0800 Subject: [PATCH 29/43] feat: auto fill metrics url when empty --- .../src/project/project.rpc.service.ts | 35 +++++++++++++++++-- apps/indexer-coordinator/src/utils/network.ts | 9 +++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/apps/indexer-coordinator/src/project/project.rpc.service.ts b/apps/indexer-coordinator/src/project/project.rpc.service.ts index 83b7c287e..8c4b32d72 100644 --- a/apps/indexer-coordinator/src/project/project.rpc.service.ts +++ b/apps/indexer-coordinator/src/project/project.rpc.service.ts @@ -6,7 +6,7 @@ import { Cron } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; import { DesiredStatus } from 'src/core/types'; import { getLogger } from 'src/utils/logger'; -import { getDomain, getIpAddress, isIp, isPrivateIp } from 'src/utils/network'; +import { getDomain, getIpAddress, isIp, isPrivateIp, safeGetDomain } from 'src/utils/network'; import { Repository } from 'typeorm'; import { RpcManifest } from './project.manifest'; import { @@ -264,13 +264,14 @@ export class ProjectRpcService { project.rateLimit = rateLimit; const manifest = project.manifest as RpcManifest; + this.fillRpcEndpoints(projectConfig.serviceEndpoints, manifest.rpcFamily); + const endpointKeys = this.getAllEndpointKeys(manifest.rpcFamily || []); project.serviceEndpoints = projectConfig.serviceEndpoints.filter((endpoint) => { return endpointKeys.includes(endpoint.key); }); projectConfig.serviceEndpoints = project.serviceEndpoints; - const validateResult = await this.validateProjectEndpoints(project, project.serviceEndpoints); if (!validateResult.valid && validateResult.level === ErrorLevel.error) { throw new Error(`Invalid endpoints: ${validateResult.reason}`); @@ -285,6 +286,36 @@ export class ProjectRpcService { return this.projectRepo.save(project); } + fillRpcEndpoints(serviceEndpoints: SeviceEndpoint[], rpcFamilyList: string[]) { + if (!serviceEndpoints.length) return; + let targetKey = ''; + let defaultSuffix = ''; + if (rpcFamilyList.includes('evm')) { + targetKey = RpcEndpointType.evmMetricsHttp; + defaultSuffix = ':6060/debug/metrics'; + } else if (rpcFamilyList.includes('polkadot')) { + targetKey = RpcEndpointType.polkadotMetricsHttp; + defaultSuffix = ':9615/metrics'; + } + if (!targetKey) return; + + let exists = false; + let value = ''; + for (const e of serviceEndpoints) { + if (e.key === targetKey) { + exists = true; + } + if (e.value) { + value = e.value; + } + } + if (value && !exists) { + const domain = safeGetDomain(value); + if (!domain) return; + serviceEndpoints.push(new SeviceEndpoint(targetKey, `http://${domain}${defaultSuffix}`)); + } + } + async stopRpcProject(id: string): Promise { const project = await this.projectService.getProject(id); if (!project) { diff --git a/apps/indexer-coordinator/src/utils/network.ts b/apps/indexer-coordinator/src/utils/network.ts index 9fdb219a1..b47ddbf96 100644 --- a/apps/indexer-coordinator/src/utils/network.ts +++ b/apps/indexer-coordinator/src/utils/network.ts @@ -25,3 +25,12 @@ export function getDomain(url: string): string { const domain = new URL(url).hostname; return domain; } + +export function safeGetDomain(url: string): string { + let domain = ''; + try { + domain = new URL(url).hostname; + } finally { + return domain; + } +} From 841257a0afcde4bf4c2acaaf85b4cc1aa4147ae5 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Mon, 7 Oct 2024 14:58:51 +0000 Subject: [PATCH 30/43] [SKIP CI] Prerelease --- apps/indexer-coordinator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/package.json b/apps/indexer-coordinator/package.json index 5fcbef4bb..ab9dfc079 100644 --- a/apps/indexer-coordinator/package.json +++ b/apps/indexer-coordinator/package.json @@ -1,6 +1,6 @@ { "name": "@subql/indexer-coordinator", - "version": "2.4.2", + "version": "2.4.3-0", "description": "", "author": "SubQuery", "license": "Apache-2.0", From fe4906c6e0a4bf42c6120eae87ea266fca2fd817 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Tue, 8 Oct 2024 01:47:18 +0000 Subject: [PATCH 31/43] [SKIP CI] Prerelease --- apps/indexer-coordinator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/package.json b/apps/indexer-coordinator/package.json index ab9dfc079..f4cb78dcd 100644 --- a/apps/indexer-coordinator/package.json +++ b/apps/indexer-coordinator/package.json @@ -1,6 +1,6 @@ { "name": "@subql/indexer-coordinator", - "version": "2.4.3-0", + "version": "2.4.3-1", "description": "", "author": "SubQuery", "license": "Apache-2.0", From 42bd9107e631416c14083ae484e4800a047c5bab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:48:05 +0000 Subject: [PATCH 32/43] build(deps): update sysinfo requirement from 0.31 to 0.32 Updates the requirements on [sysinfo](https://github.com/GuillaumeGomez/sysinfo) to permit the latest version. - [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md) - [Commits](https://github.com/GuillaumeGomez/sysinfo/compare/v0.31.0...v0.32.0) --- updated-dependencies: - dependency-name: sysinfo dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- apps/indexer-proxy/proxy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index 2cfbed00f..7f4c9f438 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -31,7 +31,7 @@ sha2 = '0.10' structopt = "0.3" subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = "v1.3.0" } subql-indexer-utils = { version = "2", path = "../utils" } -sysinfo = "0.31" +sysinfo = "0.32" tdn = { version = "0.10", default-features = false, features = ["multiple"] } tokenizers = "0.20" tokio = { version = "1", features = ["full"] } From 5a1c26d9ab4502cca117c096fba016a089d6bb18 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Tue, 8 Oct 2024 14:37:10 +0800 Subject: [PATCH 33/43] fix: close outdated channel --- apps/indexer-coordinator/src/payg/payg.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/indexer-coordinator/src/payg/payg.service.ts b/apps/indexer-coordinator/src/payg/payg.service.ts index 11625f579..0fff62429 100644 --- a/apps/indexer-coordinator/src/payg/payg.service.ts +++ b/apps/indexer-coordinator/src/payg/payg.service.ts @@ -570,11 +570,11 @@ export class PaygService implements OnModuleInit { @Cron(CronExpression.EVERY_10_MINUTES) async closeOutdatedAndNotExtended() { - const channels = await this.channelRepo.find(); + const channels = await this.getOpenChannels(); for (const c of channels) { try { const now = Math.floor(Date.now() / 1000); - if (c.expiredAt > now || c.status === ChannelStatus.TERMINATING) { + if (c.expiredAt > now) { continue; } await this.terminate(c.id); From ad5ad5ac5fc1960a85a8aab90cbf79958f0031b1 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Tue, 8 Oct 2024 15:03:56 +0800 Subject: [PATCH 34/43] feat: filter endpoints --- .../src/pages/project-details/projectDetailsPage.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/indexer-admin/src/pages/project-details/projectDetailsPage.tsx b/apps/indexer-admin/src/pages/project-details/projectDetailsPage.tsx index 9ea267a1b..baa8c8a56 100644 --- a/apps/indexer-admin/src/pages/project-details/projectDetailsPage.tsx +++ b/apps/indexer-admin/src/pages/project-details/projectDetailsPage.tsx @@ -177,9 +177,11 @@ const ProjectDetailsPage = () => { const projectStatus = useMemo(() => { if (!metadata) return ProjectStatus.Unknown; - - if (projectQuery.data?.project.projectConfig?.serviceEndpoints?.length) { - if (projectQuery.data?.project.projectConfig.serviceEndpoints.every((i) => i.valid)) { + const endpoints = projectQuery.data?.project.projectConfig?.serviceEndpoints.filter( + (i) => !['evmMetricsHttp', 'polkadotMetricsHttp'].includes(i.key) + ); + if (endpoints?.length) { + if (endpoints.every((i) => i.valid)) { return ProjectStatus.Ready; } From 6be08ec7ea0f4483aece9d330b863ad2b6944948 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Tue, 8 Oct 2024 07:13:17 +0000 Subject: [PATCH 35/43] [SKIP CI] Prerelease --- apps/indexer-coordinator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/package.json b/apps/indexer-coordinator/package.json index f4cb78dcd..1cd3e3838 100644 --- a/apps/indexer-coordinator/package.json +++ b/apps/indexer-coordinator/package.json @@ -1,6 +1,6 @@ { "name": "@subql/indexer-coordinator", - "version": "2.4.3-1", + "version": "2.4.3-2", "description": "", "author": "SubQuery", "license": "Apache-2.0", From e581db9cf11f55feb6cc12fa71ae702869f34f64 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Tue, 8 Oct 2024 17:12:38 +0800 Subject: [PATCH 36/43] feat: fill metrics endpoint --- .../src/project/project.rpc.service.ts | 64 ++++++++++++++++--- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/apps/indexer-coordinator/src/project/project.rpc.service.ts b/apps/indexer-coordinator/src/project/project.rpc.service.ts index 8c4b32d72..afbbc387a 100644 --- a/apps/indexer-coordinator/src/project/project.rpc.service.ts +++ b/apps/indexer-coordinator/src/project/project.rpc.service.ts @@ -1,7 +1,7 @@ // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { Injectable } from '@nestjs/common'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; import { DesiredStatus } from 'src/core/types'; @@ -30,12 +30,40 @@ import { const logger = getLogger('project.rpc.service'); @Injectable() -export class ProjectRpcService { +export class ProjectRpcService implements OnModuleInit { constructor( @InjectRepository(ProjectEntity) private projectRepo: Repository, private projectService: ProjectService ) {} + async onModuleInit() { + await this.fillMetricsEndpoint(); + } + + async fillMetricsEndpoint() { + const projects = await this.projectRepo.find({ where: { projectType: ProjectType.RPC } }); + for (const p of projects) { + const manifest = p.manifest as RpcManifest; + const [filled, exists] = this.fillRpcEndpoints( + p.projectConfig.serviceEndpoints, + manifest.rpcFamily + ); + const target = exists || filled; + + if (target) { + let flag = false; + let found = p.serviceEndpoints.find((s) => s.key === target.key); + if (!found) { + flag = true; + p.serviceEndpoints.push(target); + } + if (flag || filled) { + await this.projectRepo.save(p); + } + } + } + } + @Cron('0 */8 * * * *') async autoValidateRpcEndpoints() { const projects = (await this.projectService.getAliveProjects()).filter( @@ -286,8 +314,11 @@ export class ProjectRpcService { return this.projectRepo.save(project); } - fillRpcEndpoints(serviceEndpoints: SeviceEndpoint[], rpcFamilyList: string[]) { - if (!serviceEndpoints.length) return; + fillRpcEndpoints( + serviceEndpoints: SeviceEndpoint[], + rpcFamilyList: string[] + ): [filled?: SeviceEndpoint, exists?: SeviceEndpoint] { + if (!serviceEndpoints.length) return []; let targetKey = ''; let defaultSuffix = ''; if (rpcFamilyList.includes('evm')) { @@ -297,23 +328,36 @@ export class ProjectRpcService { targetKey = RpcEndpointType.polkadotMetricsHttp; defaultSuffix = ':9615/metrics'; } - if (!targetKey) return; + if (!targetKey) return []; - let exists = false; + let exists; let value = ''; for (const e of serviceEndpoints) { if (e.key === targetKey) { - exists = true; + exists = e; } if (e.value) { value = e.value; } } - if (value && !exists) { + if (exists) { + return [, exists]; + } + + let res: SeviceEndpoint | undefined; + if (value) { const domain = safeGetDomain(value); - if (!domain) return; - serviceEndpoints.push(new SeviceEndpoint(targetKey, `http://${domain}${defaultSuffix}`)); + if (!domain) return []; + res = new SeviceEndpoint( + targetKey, + `http://${domain}${defaultSuffix}`, + RpcEndpointAccessType[targetKey] || AccessType.DEFAULT + ); + res.isWebsocket = res.key.endsWith('Ws'); + res.rpcFamily = rpcFamilyList; + serviceEndpoints.push(res); } + return [res, undefined]; } async stopRpcProject(id: string): Promise { From c21f00ace2bdf2b5d2ddefb8bfcc4bd53befe05b Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Wed, 9 Oct 2024 17:12:57 +0800 Subject: [PATCH 37/43] feat: remove auto fill metrics --- .../src/project/project.rpc.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/indexer-coordinator/src/project/project.rpc.service.ts b/apps/indexer-coordinator/src/project/project.rpc.service.ts index afbbc387a..1eb56463a 100644 --- a/apps/indexer-coordinator/src/project/project.rpc.service.ts +++ b/apps/indexer-coordinator/src/project/project.rpc.service.ts @@ -37,7 +37,7 @@ export class ProjectRpcService implements OnModuleInit { ) {} async onModuleInit() { - await this.fillMetricsEndpoint(); + // await this.fillMetricsEndpoint(); } async fillMetricsEndpoint() { @@ -52,7 +52,7 @@ export class ProjectRpcService implements OnModuleInit { if (target) { let flag = false; - let found = p.serviceEndpoints.find((s) => s.key === target.key); + const found = p.serviceEndpoints.find((s) => s.key === target.key); if (!found) { flag = true; p.serviceEndpoints.push(target); @@ -292,7 +292,7 @@ export class ProjectRpcService implements OnModuleInit { project.rateLimit = rateLimit; const manifest = project.manifest as RpcManifest; - this.fillRpcEndpoints(projectConfig.serviceEndpoints, manifest.rpcFamily); + // this.fillRpcEndpoints(projectConfig.serviceEndpoints, manifest.rpcFamily); const endpointKeys = this.getAllEndpointKeys(manifest.rpcFamily || []); @@ -341,7 +341,7 @@ export class ProjectRpcService implements OnModuleInit { } } if (exists) { - return [, exists]; + return [undefined, exists]; } let res: SeviceEndpoint | undefined; From 1f4c3ee0861dc036a4f0a394bd23bd14306e4758 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Wed, 9 Oct 2024 09:24:04 +0000 Subject: [PATCH 38/43] [SKIP CI] Prerelease --- apps/indexer-coordinator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/package.json b/apps/indexer-coordinator/package.json index 1cd3e3838..302c75b2a 100644 --- a/apps/indexer-coordinator/package.json +++ b/apps/indexer-coordinator/package.json @@ -1,6 +1,6 @@ { "name": "@subql/indexer-coordinator", - "version": "2.4.3-2", + "version": "2.4.3-3", "description": "", "author": "SubQuery", "license": "Apache-2.0", From 442014571ec5aa934d7a3a034fe5d3f543dedfdf Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Thu, 10 Oct 2024 10:52:38 +0800 Subject: [PATCH 39/43] feat: v2.5.0 --- apps/indexer-coordinator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer-coordinator/package.json b/apps/indexer-coordinator/package.json index 302c75b2a..031688729 100644 --- a/apps/indexer-coordinator/package.json +++ b/apps/indexer-coordinator/package.json @@ -1,6 +1,6 @@ { "name": "@subql/indexer-coordinator", - "version": "2.4.3-3", + "version": "2.5.0", "description": "", "author": "SubQuery", "license": "Apache-2.0", From 0a194b949c7e9d5bad46c56a2e4011ce76aa7af3 Mon Sep 17 00:00:00 2001 From: luke <123917244@qq.com> Date: Thu, 10 Oct 2024 10:55:05 +0800 Subject: [PATCH 40/43] feat: update docker-compose --- deploy/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 08285f88e..a673b134a 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 coordinator: - image: subquerynetwork/indexer-coordinator:v2.4.2 + image: subquerynetwork/indexer-coordinator:v2.5.0 container_name: indexer_coordinator restart: always ports: From 1fd51a63befb53e58051a269da3b8ea379f88cc1 Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Thu, 24 Oct 2024 11:20:33 +0800 Subject: [PATCH 41/43] network-contracts 1.4.0, and sort dependencies (#511) --- apps/indexer-proxy/proxy/Cargo.toml | 2 +- apps/indexer-proxy/utils/Cargo.toml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index 7f4c9f438..eff430ceb 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -29,7 +29,7 @@ serde_json = "1.0" serde_with ={ version = "3.0", features = ["json"] } sha2 = '0.10' structopt = "0.3" -subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = "v1.3.0" } +subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = "v1.4.0" } subql-indexer-utils = { version = "2", path = "../utils" } sysinfo = "0.32" tdn = { version = "0.10", default-features = false, features = ["multiple"] } diff --git a/apps/indexer-proxy/utils/Cargo.toml b/apps/indexer-proxy/utils/Cargo.toml index 3a0df0c91..d51b4c03b 100644 --- a/apps/indexer-proxy/utils/Cargo.toml +++ b/apps/indexer-proxy/utils/Cargo.toml @@ -4,21 +4,21 @@ version = "2.1.0" edition = "2021" [dependencies] -subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = "v1.3.0" } axum = "0.7" +base64 = "0.22" bincode = "1.3" -blake3="1.3" +blake3 = "1.3" bs58 = "0.5" -base64 = "0.22" +ethereum-types = "0.15" +ethers = { git = "https://github.com/gakonst/ethers-rs.git", tag = "ethers-v2.0.7" } hex = "0.4" +http = "1.1.0" +once_cell = "1.12" rand_chacha = "0.3" reqwest = { version = "0.12", features = ["json"] } rustc-hex = "2.1" -once_cell = "1.12" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_with={ version = "3.0", features = ["json"] } +serde_with ={ version = "3.0", features = ["json"] } +subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = "v1.4.0" } uint = "0.10" -ethers = { git = "https://github.com/gakonst/ethers-rs.git", tag = "ethers-v2.0.7" } -ethereum-types = "0.15" -http = "1.1.0" From 3898b485b37c0e3921b6debf135d4e629703ed7a Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Thu, 24 Oct 2024 11:21:21 +0800 Subject: [PATCH 42/43] feat: add chs_signature and indexer_signature (#506) * feat: add chs_signature and indexer_signature * sentry log return data is empty * 3.6.3-beta.1 * fix typo * fix: syntax * add code * delete unused struct * revert code --------- Co-authored-by: luke <123917244@qq.com> --- apps/indexer-proxy/proxy/src/payg.rs | 34 ++++++++++++++------------ apps/indexer-proxy/proxy/src/server.rs | 11 +++++++++ 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/apps/indexer-proxy/proxy/src/payg.rs b/apps/indexer-proxy/proxy/src/payg.rs index f7314d6a3..e185e1de4 100644 --- a/apps/indexer-proxy/proxy/src/payg.rs +++ b/apps/indexer-proxy/proxy/src/payg.rs @@ -768,16 +768,31 @@ pub async fn extend_channel( // send to coordinator let expired_at = expired + expiration as i64; + let account = ACCOUNT.read().await; + let indexer_sign = extend_sign2( + channel_id, + indexer, + state_cache.agent, + new_price, + U256::from(expired), + U256::from(expiration), + &account.controller, + ) + .await?; + drop(account); + let indexer_sign = convert_sign_to_string(&indexer_sign); let mdata = format!( r#"mutation {{ channelExtend( id:"{:#X}", expiration:{}, price:"{}", - ) + indexerSign:"0x{}", + consumerSign:"0x{}" + ) {{ id, expiredAt }} }}"#, - channel_id, expired_at, new_price, + channel_id, expired_at, new_price, indexer_sign, signature ); let url = COMMAND.graphql_url(); let query = GraphQLQuery::query(&mdata); @@ -790,20 +805,7 @@ pub async fn extend_channel( return Err(Error::ServiceException(1202)); } - let account = ACCOUNT.read().await; - let indexer_sign = extend_sign2( - channel_id, - indexer, - state_cache.agent, - new_price, - U256::from(expired), - U256::from(expiration), - &account.controller, - ) - .await?; - drop(account); - - Ok(convert_sign_to_string(&indexer_sign)) + Ok(indexer_sign) } pub async fn pay_channel(mut state: QueryState) -> Result { diff --git a/apps/indexer-proxy/proxy/src/server.rs b/apps/indexer-proxy/proxy/src/server.rs index 674fefc1d..d47fdaf06 100644 --- a/apps/indexer-proxy/proxy/src/server.rs +++ b/apps/indexer-proxy/proxy/src/server.rs @@ -528,6 +528,17 @@ async fn ep_payg_handler( let (body, mut headers) = match res_fmt.to_str() { Ok("inline") => { let return_body = if let Ok(return_data) = String::from_utf8(data.clone()) { + if return_data.is_empty() { + let unique_title = format!( + "payg ep_query_handler, inline returns empty, because endpoint returns empty, deployment_id: {}, ep_name: {}", + deployment, ep_name + ); + let msg = format!( + "res_fmt: {:#?}, headers: {:#?}, body: {}, data: {:?}", + res_fmt, headers, body, data + ); + make_sentry_message(&unique_title, &msg); + } return_data } else { let unique_title = format!( From 372a3ffaeeb2d7b74da21c01c7af370ebbedf74a Mon Sep 17 00:00:00 2001 From: gerald <3949379+getong@users.noreply.github.com> Date: Thu, 24 Oct 2024 11:33:46 +0800 Subject: [PATCH 43/43] indexer-proxy 2.7.0, indexer-utils 2.2.0 (#533) --- apps/indexer-proxy/proxy/Cargo.toml | 2 +- apps/indexer-proxy/utils/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/indexer-proxy/proxy/Cargo.toml b/apps/indexer-proxy/proxy/Cargo.toml index eff430ceb..3ae8e7b4a 100644 --- a/apps/indexer-proxy/proxy/Cargo.toml +++ b/apps/indexer-proxy/proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subql-indexer-proxy" -version = "2.6.3-beta.2" +version = "2.7.0" edition = "2021" [dependencies] diff --git a/apps/indexer-proxy/utils/Cargo.toml b/apps/indexer-proxy/utils/Cargo.toml index d51b4c03b..94adfd5c5 100644 --- a/apps/indexer-proxy/utils/Cargo.toml +++ b/apps/indexer-proxy/utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subql-indexer-utils" -version = "2.1.0" +version = "2.2.0" edition = "2021" [dependencies]