diff --git a/apollo/.env b/apollo/.env index bf79de85..cf3f4357 100644 --- a/apollo/.env +++ b/apollo/.env @@ -1,2 +1,22 @@ SUBQL=https://api.subquery.network/sq/helix-bridge/ -THEGRAPH=https://crab-thegraph.darwinia.network/subgraphs/name/wormhole/Sub2SubMappingTokenFactory \ No newline at end of file +THEGRAPH=https://crab-thegraph.darwinia.network/subgraphs/name/wormhole/Sub2SubMappingTokenFactory +FETCH_DARWINIA_TO_CRAB_LOCK_DATA_INTERVAL=10000 +UPDATE_DARWINIA_TO_CRAB_LOCK_DATA_INTERVAL=5000 +FETCH_DARWINIA_TO_CRAB_LOCK_DATA_FIRST=10 +UPDATE_DARWINIA_TO_CRAB_LOCK_DATA_FIRST=10 +FETCH_DARWINIA_TO_CRAB_BURN_DATA_INTERVAL=10000 +UPDATE_DARWINIA_TO_CRAB_BURN_DATA_INTERVAL=15000 +FETCH_DARWINIA_TO_CRAB_BURN_DATA_FIRST=10 +UPDATE_DARWINIA_TO_CRAB_BURN_DATA_FIRST=10 + +FETCH_CRAB_TO_CRAB_DVM_DATA_INTERVAL=10000 +FETCH_CRAB_TO_CRAB_DVM_DATA_FIRST=10 + +# This was inserted by `prisma init`: +# Environment variables declared in this file are automatically made available to Prisma. +# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema + +# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB (Preview). +# See the documentation for all the connection string options: https://pris.ly/d/connection-strings + +DATABASE_URL="postgresql://admin:admin@localhost:5432/apollo_graph?schema=public" diff --git a/apollo/package.json b/apollo/package.json index 3e85b985..4bd366c2 100644 --- a/apollo/package.json +++ b/apollo/package.json @@ -28,12 +28,15 @@ "@nestjs/core": "^8.0.0", "@nestjs/graphql": "^10.0.9", "@nestjs/platform-express": "^8.0.0", + "@nestjs/schedule": "^1.1.0", + "@prisma/client": "^3.14.0", "apollo-server-express": "^3.6.7", "apollo-type-bigint": "^0.1.3", "axios": "^0.26.1", "date-fns": "^2.28.0", "graphql": "^16.3.0", "lodash": "^4.17.21", + "pg": "^8.7.3", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" @@ -50,6 +53,7 @@ "@vercel/ncc": "^0.33.4", "copyfiles": "^2.4.1", "jest": "^27.2.5", + "prisma": "^3.14.0", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "^27.0.3", diff --git a/apollo/prisma/migrations/20220512130618_init/migration.sql b/apollo/prisma/migrations/20220512130618_init/migration.sql new file mode 100644 index 00000000..c716f14c --- /dev/null +++ b/apollo/prisma/migrations/20220512130618_init/migration.sql @@ -0,0 +1,21 @@ +-- CreateTable +CREATE TABLE "HistoryRecord" ( + "id" TEXT NOT NULL, + "fromChain" TEXT NOT NULL, + "toChain" TEXT NOT NULL, + "bridge" TEXT NOT NULL, + "laneId" TEXT NOT NULL, + "nonce" TEXT NOT NULL, + "requestTxHash" TEXT NOT NULL, + "responseTxHash" TEXT, + "sender" TEXT NOT NULL, + "recipient" TEXT NOT NULL, + "token" TEXT NOT NULL, + "amount" TEXT NOT NULL, + "startTime" INTEGER NOT NULL, + "endTime" INTEGER, + "result" INTEGER NOT NULL, + "fee" TEXT NOT NULL, + + CONSTRAINT "HistoryRecord_pkey" PRIMARY KEY ("id") +); diff --git a/apollo/prisma/migrations/migration_lock.toml b/apollo/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..fbffa92c --- /dev/null +++ b/apollo/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/apollo/prisma/schema.prisma b/apollo/prisma/schema.prisma new file mode 100644 index 00000000..b2db5ee5 --- /dev/null +++ b/apollo/prisma/schema.prisma @@ -0,0 +1,30 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model HistoryRecord { + id String @id + fromChain String + toChain String + bridge String + laneId String + nonce String + requestTxHash String + responseTxHash String? + sender String + recipient String + token String + amount String + startTime Int + endTime Int? + result Int + fee String +} diff --git a/apollo/src/aggregation/aggregation.history.graphql b/apollo/src/aggregation/aggregation.history.graphql new file mode 100644 index 00000000..02d24648 --- /dev/null +++ b/apollo/src/aggregation/aggregation.history.graphql @@ -0,0 +1,25 @@ + +type HistoryRecord { + id: String! + fromChain: String! + toChain: String! + bridge: String! + laneId: String! + nonce: String! + requestTxHash: String! + responseTxHash: String + sender: String! + recipient: String! + token: String! + amount: String! + startTime: Int! + endTime: Int + result: Int! + fee: String! +} + +type Query { + historyRecordById(id: String): HistoryRecord + historyRecords(sender: String, recipient: String, row: Int, page: Int): [HistoryRecord] +} + diff --git a/apollo/src/aggregation/aggregation.module.ts b/apollo/src/aggregation/aggregation.module.ts new file mode 100644 index 00000000..337cadfd --- /dev/null +++ b/apollo/src/aggregation/aggregation.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { AggregationService } from './aggregation.service'; +import { AggregationResolver } from './aggregation.resolver'; + +@Module({ + providers: [AggregationService, AggregationResolver], + exports: [AggregationService] +}) +export class AggregationModule {} diff --git a/apollo/src/aggregation/aggregation.resolver.ts b/apollo/src/aggregation/aggregation.resolver.ts new file mode 100644 index 00000000..9b9c1595 --- /dev/null +++ b/apollo/src/aggregation/aggregation.resolver.ts @@ -0,0 +1,42 @@ +import { Args, Query, Resolver } from '@nestjs/graphql'; +import { AggregationService } from './aggregation.service'; +import { HistoryRecord, Prisma, PrismaClient } from '@prisma/client'; + +@Resolver() +export class AggregationResolver { + constructor(private aggregationService: AggregationService) {} + + @Query() + async historyRecordById( + @Args('id') id: string + ) { + return this.aggregationService.queryHistoryRecordById({ + id: id, + }); + } + + @Query() + async historyRecords( + @Args('sender') sender: string, + @Args('recipient') recipient: string, + @Args('row') row: number, + @Args('page') page: number + ) { + let skip = row * page || undefined; + let take = row || undefined; + let filter = new Array(); + if (sender) { + filter.push({ sender: sender }); + } + if (recipient) { + filter.push({ recipient: recipient }); + } + + let where = (sender || recipient) ? { OR: filter } : undefined; + return this.aggregationService.queryHistoryRecords({ + skip, + take, + where, + }); + } +} diff --git a/apollo/src/aggregation/aggregation.service.ts b/apollo/src/aggregation/aggregation.service.ts new file mode 100644 index 00000000..3625fc5e --- /dev/null +++ b/apollo/src/aggregation/aggregation.service.ts @@ -0,0 +1,64 @@ +import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common'; +import { HistoryRecord, Prisma, PrismaClient } from '@prisma/client'; + +@Injectable() +export class AggregationService extends PrismaClient implements OnModuleInit { + async onModuleInit() { + await this.$connect(); + } + + async enableShutdownHooks(app: INestApplication) { + this.$on('beforeExit', async () => { + await app.close(); + }); + } + + async createHistoryRecord(data: Prisma.HistoryRecordCreateInput): Promise { + return this.historyRecord.create({ + data, + }); + } + + async updateHistoryRecord(params: { + where: Prisma.HistoryRecordWhereUniqueInput, + data: Prisma.HistoryRecordUpdateInput, + }): Promise { + const { where, data } = params; + return this.historyRecord.update({ + data, + where, + }); + } + + async queryHistoryRecordById( + historyRecordWhereUniqueInput: Prisma.HistoryRecordWhereUniqueInput, + ): Promise { + return this.historyRecord.findUnique({ + where: historyRecordWhereUniqueInput, + }); + } + + async queryHistoryRecordFirst( + historyRecordWhereInput: Prisma.HistoryRecordWhereInput, + ): Promise { + return this.historyRecord.findFirst({ + where: historyRecordWhereInput, + orderBy: { startTime: 'desc' } + }); + } + + // const count = this.historyRecord.count(); + async queryHistoryRecords(params: { + skip?: number; + take?: number; + where?: Prisma.HistoryRecordWhereInput; + }): Promise { + const { skip, take, where } = params; + return this.historyRecord.findMany({ + skip, + take, + where, + orderBy: {startTime: 'desc'} + }); + } +} diff --git a/apollo/src/app.module.ts b/apollo/src/app.module.ts index b40dd502..117ce383 100644 --- a/apollo/src/app.module.ts +++ b/apollo/src/app.module.ts @@ -8,6 +8,11 @@ import { AccountModule } from './account/account.module'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { Substrate2substrateModule } from './substrate2substrate/substrate2substrate.module'; +import { TasksModule } from './tasks/tasks.module'; +import { ScheduleModule } from '@nestjs/schedule'; +import { AggregationModule } from './aggregation/aggregation.module'; +import { Darwinia2crabModule } from './darwinia2crab/darwinia2crab.module'; +import { Crab2smartModule } from './crab2smart/crab2smart.module'; @Scalar('BigInt') export class BigIntScalar extends BigInt {} @@ -16,7 +21,7 @@ export class BigIntScalar extends BigInt {} imports: [ GraphQLModule.forRoot({ driver: ApolloDriver, - typePaths: ['./**/*.graphql'], + typePaths: ['./src/**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', @@ -25,6 +30,11 @@ export class BigIntScalar extends BigInt {} Substrate2substrateModule, AccountModule, ConfigModule.forRoot(), + ScheduleModule.forRoot(), + TasksModule, + AggregationModule, + Darwinia2crabModule, + Crab2smartModule, ], controllers: [AppController], providers: [AppService, BigIntScalar], diff --git a/apollo/src/crab2smart/crab2smart.module.ts b/apollo/src/crab2smart/crab2smart.module.ts new file mode 100644 index 00000000..7a41be6b --- /dev/null +++ b/apollo/src/crab2smart/crab2smart.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { Crab2smartService } from './crab2smart.service'; +import { AggregationModule } from '../aggregation/aggregation.module'; +import { TasksModule } from '../tasks/tasks.module'; + +@Module({ + imports: [ ConfigModule, AggregationModule, TasksModule ], + providers: [Crab2smartService] +}) +export class Crab2smartModule {} diff --git a/apollo/src/crab2smart/crab2smart.service.ts b/apollo/src/crab2smart/crab2smart.service.ts new file mode 100644 index 00000000..f477671f --- /dev/null +++ b/apollo/src/crab2smart/crab2smart.service.ts @@ -0,0 +1,73 @@ +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { AggregationService } from '../aggregation/aggregation.service'; +import { TasksService } from '../tasks/tasks.service'; +import axios from 'axios'; +import { getUnixTime } from 'date-fns'; + +@Injectable() +export class Crab2smartService implements OnModuleInit { + private readonly logger = new Logger(TasksService.name); + + private readonly crabUrl = this.configService.get('SUBQL') + 'crab'; + private readonly fetchDataInterval = this.configService.get('FETCH_CRAB_TO_CRAB_DVM_DATA_INTERVAL'); + private readonly fetchDataFirst = this.configService.get('FETCH_CRAB_TO_CRAB_DVM_DATA_FIRST'); + + constructor( + private configService: ConfigService, + private aggregationService: AggregationService, + private taskService: TasksService + ) {} + + async onModuleInit() { + const self = this; + this.taskService.addInterval('crab2crabdvm-fetchdata', this.fetchDataInterval, function() { + self.fetchRecords() + }); + } + + async fetchRecords() { + const first = this.fetchDataFirst; + try { + let firstRecord = await this.aggregationService.queryHistoryRecordFirst({ + OR: [ + { fromChain: 'crab', toChain: 'crab-dvm' }, + { fromChain: 'crab-dvm', toChain: 'crab' } + ], + bridge: 'helix', + }); + var latestNonce: number = firstRecord ? Number(firstRecord.nonce) : 0; + const res = await axios.post(this.crabUrl, { + query: `query { transfers (first: ${first}, orderBy: TIMESTAMP_ASC, offset: ${latestNonce}) { totalCount nodes{id, senderId, recipientId, fromChain, toChain, amount, timestamp }}}`, + variables: null, + }); + const nodes = res.data?.data?.transfers?.nodes; + if (nodes) { + for (let node of nodes) { + latestNonce = latestNonce + 1; + await this.aggregationService.createHistoryRecord({ + id: 'crab2crabdvm-' + node.id, + fromChain: node.fromChain, + toChain: node.toChain, + bridge: 'helix', + laneId: "0", + nonce: latestNonce.toString(), + requestTxHash: node.id, + responseTxHash: node.id, + sender: node.senderId, + recipient: node.recipientId, + token: "Crab", + amount: node.amount, + startTime: getUnixTime(new Date(node.timestamp)), + endTime: getUnixTime(new Date(node.timestamp)), + result: 1, + fee: "0", + }); + } + } + this.logger.log(`save new Darwinia to Crab lock records success, latestNonce: ${latestNonce}, added: ${nodes.length}`); + } catch(e) { + this.logger.warn(`update Crab to Crab DVM records failed ${e}`); + } + } +} diff --git a/apollo/src/darwinia2crab/darwinia2crab.graphql b/apollo/src/darwinia2crab/darwinia2crab.graphql new file mode 100644 index 00000000..66d334e2 --- /dev/null +++ b/apollo/src/darwinia2crab/darwinia2crab.graphql @@ -0,0 +1,26 @@ +scalar BigInt +scalar ID +scalar JSON + +type S2sEvent { + id: ID! + laneId: String! + nonce: String! + requestTxHash: String! # TokenLocked tx hash + responseTxHash: String # TokenLockedConfirmed tx hash + senderId: String! + result: Int! # 0 TokenLocked 1 TokenLockedConfirmed success 2 TokenLockedConfirmed fail + recipient: String! + token: String! # token address + amount: String! + startTimestamp: String! + endTimestamp: String + fee: String! +} + +type DailyStatistic { + id: ID! + dailyVolume: BigInt + dailyCount: Int +} + diff --git a/apollo/src/darwinia2crab/darwinia2crab.module.ts b/apollo/src/darwinia2crab/darwinia2crab.module.ts new file mode 100644 index 00000000..3c3b22fd --- /dev/null +++ b/apollo/src/darwinia2crab/darwinia2crab.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { Darwinia2crabService } from './darwinia2crab.service'; +import { AggregationModule } from '../aggregation/aggregation.module'; +import { TasksModule } from '../tasks/tasks.module'; + +@Module({ + imports: [ ConfigModule, AggregationModule, TasksModule ], + providers: [Darwinia2crabService] +}) +export class Darwinia2crabModule {} diff --git a/apollo/src/darwinia2crab/darwinia2crab.service.ts b/apollo/src/darwinia2crab/darwinia2crab.service.ts new file mode 100644 index 00000000..b504f853 --- /dev/null +++ b/apollo/src/darwinia2crab/darwinia2crab.service.ts @@ -0,0 +1,247 @@ +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { AggregationService } from '../aggregation/aggregation.service'; +import { TasksService } from '../tasks/tasks.service'; +import axios from 'axios'; +import { getUnixTime } from 'date-fns'; +import { + BurnRecordEntity, +} from '../graphql'; + +@Injectable() +export class Darwinia2crabService implements OnModuleInit { + private readonly logger = new Logger(TasksService.name); + + // lock and mint + private readonly darwiniaUrl = this.configService.get('SUBQL') + 'darwinia'; + private readonly fetchLockDataInterval = this.configService.get('FETCH_DARWINIA_TO_CRAB_LOCK_DATA_INTERVAL'); + private readonly updateLockDataInterval = this.configService.get('UPDATE_DARWINIA_TO_CRAB_LOCK_DATA_INTERVAL'); + private readonly fetchLockDataFirst = this.configService.get('FETCH_DARWINIA_TO_CRAB_LOCK_DATA_FIRST'); + private readonly updateLockDataFirst = this.configService.get('UPDATE_DARWINIA_TO_CRAB_LOCK_DATA_FIRST'); + private needSyncLockConfirmed = true; + + // burn and redeem + private readonly crabUrl = this.configService.get('THEGRAPH'); + private readonly fetchBurnDataInterval = this.configService.get('FETCH_DARWINIA_TO_CRAB_BURN_DATA_INTERVAL'); + private readonly updateBurnDataInterval = this.configService.get('UPDATE_DARWINIA_TO_CRAB_BURN_DATA_INTERVAL'); + private readonly fetchBurnDataFirst = this.configService.get('FETCH_DARWINIA_TO_CRAB_BURN_DATA_FIRST'); + private readonly updateBurnDataFirst = this.configService.get('UPDATE_DARWINIA_TO_CRAB_BURN_DATA_FIRST'); + private needSyncBurnConfirmed = true; + constructor( + private configService: ConfigService, + private aggregationService: AggregationService, + private taskService: TasksService + ) {} + + async onModuleInit() { + const self = this; + this.taskService.addInterval('darwinia2crabdvm-fetchlockdata', this.fetchLockDataInterval, function() { + self.fetchLockRecords() + }); + this.taskService.addInterval('darwinia2crabdvm-updatelockdata', this.updateLockDataInterval, function() { + self.checkConfirmedLockRecords() + }); + this.taskService.addInterval('darwinia2crabdvm-fetchburndata', this.fetchBurnDataInterval, function() { + self.fetchBurnRecords() + }); + this.taskService.addInterval('darwinia2crabdvm-updateburndata', this.updateBurnDataInterval, function() { + self.checkConfirmedBurnRecords() + }); + } + + async fetchLockRecords() { + const first = this.fetchLockDataFirst; + try { + let firstRecord = await this.aggregationService.queryHistoryRecordFirst({ + fromChain: 'darwinia', + toChain: 'crab-dvm', + bridge: 'helix', + }); + const latestNonce = firstRecord ? firstRecord.nonce : -1; + const res = await axios.post(this.darwiniaUrl, { + query: `query { s2sEvents (first: ${first}, orderBy: NONCE_ASC, filter: {nonce: {greaterThan: "${latestNonce}"}}) {totalCount nodes{id, laneId, nonce, amount, startTimestamp, endTimestamp, requestTxHash, responseTxHash, result, token, senderId, recipient, fee}}}`, + variables: null, + }); + const nodes = res.data?.data?.s2sEvents?.nodes; + if (nodes) { + for (let node of nodes) { + await this.aggregationService.createHistoryRecord({ + id: 'darwinia2crabdvm-lock-' + node.id, + fromChain: 'darwinia', + toChain: 'crab-dvm', + bridge: 'helix', + laneId: node.laneId, + nonce: node.nonce, + requestTxHash: node.requestTxHash, + responseTxHash: node.responseTxHash, + sender: node.senderId, + recipient: node.recipient, + token: node.token, + amount: node.amount, + startTime: getUnixTime(new Date(node.startTimestamp)), + endTime: getUnixTime(new Date(node.endTimestamp)), + result: node.result, + fee: node.fee, + }); + if (!this.needSyncLockConfirmed && node.result == 0) { + this.needSyncLockConfirmed = true; + } + } + } + this.logger.log(`save new Darwinia to Crab lock records success, latestNonce: ${latestNonce}, added: ${nodes.length}`); + } catch (e) { + this.logger.warn(`fetch Darwinia to Crab lock records failed ${e}`); + } + } + + async checkConfirmedLockRecords() { + if (!this.needSyncLockConfirmed) { + return; + } + const first = this.updateLockDataFirst; + try { + let unconfirmedRecords = await this.aggregationService.queryHistoryRecords({ + take: Number(first), + where: { + fromChain: 'darwinia', + toChain: 'crab-dvm', + bridge: 'helix', + result: 0, + } + }); + if (unconfirmedRecords.length == 0) { + this.needSyncLockConfirmed = false; + return + } + var targetNonces = new Array(); + for ( let record of unconfirmedRecords) { + targetNonces.push("\"" + record.nonce + "\"") + } + let nonces = targetNonces.join(","); + const res = await axios.post(this.darwiniaUrl, { + query: `query { s2sEvents (filter: {nonce: {in: [${nonces}]}}) {totalCount nodes{id, laneId, nonce, amount, startTimestamp, endTimestamp, requestTxHash, responseTxHash, result, token, senderId, recipient, fee}}}`, + variables: null, + }); + const nodes = res.data?.data?.s2sEvents?.nodes; + if (nodes) { + for (let node of nodes) { + if (node.result == 0) { + continue; + } + await this.aggregationService.updateHistoryRecord({ + where: { + id: 'darwinia2crabdvm-lock-' + node.id, + }, + data: { + responseTxHash: node.responseTxHash, + endTime: getUnixTime(new Date(node.endTimestamp)), + result: node.result, + }, + }); + } + this.logger.log(`update Darwinia to Crab lock records success, nonces: ${nonces}`); + } + } catch (e) { + this.logger.warn(`update Darwinia to Crab lock records failed ${e}`); + } + } + + // burn + async fetchBurnRecords() { + const first = this.fetchBurnDataFirst; + try { + let firstRecord = await this.aggregationService.queryHistoryRecordFirst({ + fromChain: 'crab-dvm', + toChain: 'darwinia', + bridge: 'helix', + }); + const latestNonce = firstRecord ? firstRecord.nonce : -1; + const res = await axios.post(this.crabUrl, { + query: `query { burnRecordEntities (first: ${first}, orderBy: nonce, orderDirection: asc, where: { nonce_gt: ${latestNonce} }) {id, lane_id, nonce, amount, start_timestamp, end_timestamp, request_transaction, response_transaction, result, token, sender, recipient, fee}}`, + variables: null, + }); + const nodes = res.data?.data?.burnRecordEntities; + if (nodes) { + for (let node of nodes) { + await this.aggregationService.createHistoryRecord({ + id: 'darwinia2crabdvm-burn-' + node.id, + fromChain: 'crab-dvm', + toChain: 'darwinia', + bridge: 'helix', + laneId: node.lane_id, + nonce: node.nonce, + requestTxHash: node.request_transaction, + responseTxHash: node.response_transaction, + sender: node.sender, + recipient: node.recipient, + token: node.token, + amount: node.amount, + startTime: Number(node.start_timestamp), + endTime: Number(node.end_timestamp), + result: node.result, + fee: node.fee.toString(), + }); + if (!this.needSyncBurnConfirmed && node.result == 0) { + this.needSyncBurnConfirmed = true; + } + } + this.logger.log(`save new Darwinia to Crab lock records success, latestNonce: ${latestNonce}, added: ${nodes.length}`); + } + } catch (e) { + this.logger.warn(`fetch Darwinia to Crab lock records failed ${e}`); + } + } + + async checkConfirmedBurnRecords() { + if (!this.needSyncBurnConfirmed) { + return; + } + const take = this.updateBurnDataFirst; + try { + let unconfirmedRecords = await this.aggregationService.queryHistoryRecords({ + take: Number(take), + where: { + fromChain: 'crab-dvm', + toChain: 'darwinia', + bridge: 'helix', + result: 0, + } + }); + if (unconfirmedRecords.length <= 1) { + this.needSyncBurnConfirmed = false; + return + } + var targetNonces = new Array(); + for ( let record of unconfirmedRecords) { + targetNonces.push(record.nonce) + } + let nonces = targetNonces.join(","); + const res = await axios.post(this.crabUrl, { + query: `query { burnRecordEntities (where: { nonce_in: [${nonces}] }) {id, lane_id, nonce, amount, start_timestamp, end_timestamp, request_transaction, response_transaction, result, token, sender, recipient, fee}}`, + variables: null, + }); + const nodes = res.data?.data?.burnRecordEntities; + if (nodes) { + for (let node of nodes) { + if (node.result == 0) { + continue; + } + await this.aggregationService.updateHistoryRecord({ + where: { + id: 'darwinia2crabdvm-burn-' + node.id, + }, + data: { + responseTxHash: node.response_transaction, + endTime: Number(node.end_timestamp), + result: node.result, + }, + }); + } + this.logger.log(`update Darwinia to Crab burn records success, nonces: ${nonces}`); + } + } catch (e) { + this.logger.warn(`update Darwinia to Crab burn records failed ${e}`); + } + } +} + diff --git a/apollo/src/graphql.ts b/apollo/src/graphql.ts index 3206cd05..c03c81d5 100644 --- a/apollo/src/graphql.ts +++ b/apollo/src/graphql.ts @@ -1,3 +1,4 @@ + /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) @@ -13,6 +14,10 @@ export class Accounts { export abstract class IQuery { abstract accounts(chain?: Nullable): Nullable | Promise>; + abstract historyRecordById(id?: Nullable): Nullable | Promise>; + + abstract historyRecords(sender?: Nullable, recipient?: Nullable, row?: Nullable, page?: Nullable): Nullable[]> | Promise[]>>; + abstract burnRecords(first?: Nullable, startTime?: Nullable, sender?: Nullable, recipient?: Nullable): Nullable[]> | Promise[]>>; abstract burnRecord(id: string): Nullable | Promise>; @@ -30,6 +35,47 @@ export abstract class IQuery { abstract dailyStatistics(first?: Nullable, timepast?: Nullable, chain?: Nullable): Nullable[]> | Promise[]>>; } +export class HistoryRecord { + id: string; + fromChain: string; + toChain: string; + bridge: string; + laneId: string; + nonce: string; + requestTxHash: string; + responseTxHash?: Nullable; + sender: string; + recipient: string; + token: string; + amount: string; + startTime: number; + endTime?: Nullable; + result: number; + fee: string; +} + +export class S2sEvent { + id: string; + laneId: string; + nonce: string; + requestTxHash: string; + responseTxHash?: Nullable; + senderId: string; + result: number; + recipient: string; + token: string; + amount: string; + startTimestamp: string; + endTimestamp?: Nullable; + fee: string; +} + +export class DailyStatistic { + id: string; + dailyVolume?: Nullable; + dailyCount?: Nullable; +} + export class BurnRecordEntity { id: string; lane_id: string; @@ -56,22 +102,6 @@ export class DVMLockRecord { txHash: string; } -export class S2sEvent { - id: string; - laneId: string; - nonce: string; - requestTxHash: string; - responseTxHash?: Nullable; - senderId: string; - result: number; - recipient: string; - token: string; - amount: string; - startTimestamp: string; - endTimestamp?: Nullable; - fee: string; -} - export class UnlockRecord { id: string; laneId: string; @@ -105,12 +135,6 @@ export class S2sRecord { fee: string; } -export class DailyStatistic { - id: string; - dailyVolume?: Nullable; - dailyCount?: Nullable; -} - export class BurnRecordEntity_filter { start_timestamp_lt?: Nullable; } diff --git a/apollo/src/tasks/tasks.module.ts b/apollo/src/tasks/tasks.module.ts new file mode 100644 index 00000000..87b0e32c --- /dev/null +++ b/apollo/src/tasks/tasks.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TasksService } from './tasks.service'; +import { AggregationModule } from '../aggregation/aggregation.module'; + +@Module({ + imports: [ AggregationModule ], + providers: [TasksService], + exports: [TasksService] +}) +export class TasksModule {} diff --git a/apollo/src/tasks/tasks.service.ts b/apollo/src/tasks/tasks.service.ts new file mode 100644 index 00000000..578c63d0 --- /dev/null +++ b/apollo/src/tasks/tasks.service.ts @@ -0,0 +1,15 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Interval, SchedulerRegistry } from '@nestjs/schedule'; +import { AggregationService } from '../aggregation/aggregation.service'; + +@Injectable() +export class TasksService { + private readonly logger = new Logger(TasksService.name); + constructor(private schedulerRegistry: SchedulerRegistry, private aggregationService: AggregationService) {} + + addInterval(name: string, milliseconds: number, callback: () => void) { + this.logger.log(`new interval task added name:${name}, ms: ${milliseconds}`); + const interval = setInterval(callback, milliseconds); + this.schedulerRegistry.addInterval(name, interval); + } +} diff --git a/apollo/yarn.lock b/apollo/yarn.lock index 71f63ab0..4de6b52e 100644 --- a/apollo/yarn.lock +++ b/apollo/yarn.lock @@ -737,6 +737,14 @@ multer "1.4.4" tslib "2.3.1" +"@nestjs/schedule@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@nestjs/schedule/-/schedule-1.1.0.tgz#7c8e937399bf5da3d6895e7179ae4bdc4377906e" + integrity sha512-0QpbwClUildXqlyoaygG+aIQZNNMv31XDyQxX+Ob1zw/3I8+AVrDlBwZHQ+tlhIcJFR8aG+VTH8xwIjXwtS1UA== + dependencies: + cron "1.8.2" + uuid "8.3.2" + "@nestjs/schematics@^8.0.0", "@nestjs/schematics@^8.0.3": version "8.0.10" resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-8.0.10.tgz#4f951ea1ac94721ba6a6ccfae6461f8b22949573" @@ -786,6 +794,23 @@ consola "^2.15.0" node-fetch "^2.6.1" +"@prisma/client@^3.14.0": + version "3.14.0" + resolved "https://registry.npmjs.org/@prisma/client/-/client-3.14.0.tgz#bb90405c012fcca11f4647d91153ed4c58f3bd48" + integrity sha512-atb41UpgTR1MCst0VIbiHTMw8lmXnwUvE1KyUCAkq08+wJyjRE78Due+nSf+7uwqQn+fBFYVmoojtinhlLOSaA== + dependencies: + "@prisma/engines-version" "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a" + +"@prisma/engines-version@3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a": + version "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a" + resolved "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a.tgz#4edae57cf6527f35e22cebe75e49214fc0e99ac9" + integrity sha512-D+yHzq4a2r2Rrd0ZOW/mTZbgDIkUkD8ofKgusEI1xPiZz60Daks+UM7Me2ty5FzH3p/TgyhBpRrfIHx+ha20RQ== + +"@prisma/engines@3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a": + version "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a" + resolved "https://registry.npmjs.org/@prisma/engines/-/engines-3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a.tgz#7fa11bc26a51d450185c816cc0ab8cac673fb4bf" + integrity sha512-LwZvI3FY6f43xFjQNRuE10JM5R8vJzFTSmbV9X0Wuhv9kscLkjRlZt0BEoiHmO+2HA3B3xxbMfB5du7ZoSFXGg== + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -1127,6 +1152,11 @@ dependencies: "@types/yargs-parser" "*" +"@vercel/ncc@^0.33.4": + version "0.33.4" + resolved "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.33.4.tgz#e44a87511f583b7ba88e4b9ae90eeb7ba252b872" + integrity sha512-ln18hs7dMffelP47tpkaR+V5Tj6coykNyxJrlcmCormPqRQjB/Gv4cu2FfBG+PMzIfdZp2CLDsrrB1NPU22Qhg== + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -1722,6 +1752,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -2040,6 +2075,13 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cron@1.8.2: + version "1.8.2" + resolved "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz#4ac5e3c55ba8c163d84f3407bde94632da8370ce" + integrity sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg== + dependencies: + moment-timezone "^0.5.x" + cross-spawn@^7.0.0, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -3711,6 +3753,18 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +moment-timezone@^0.5.x: + version "0.5.34" + resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c" + integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0": + version "2.29.3" + resolved "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -3916,6 +3970,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -3983,6 +4042,57 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pg-connection-string@^2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" + integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.5.1: + version "3.5.1" + resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz#f499ce76f9bf5097488b3b83b19861f28e4ed905" + integrity sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ== + +pg-protocol@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" + integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.7.3: + version "8.7.3" + resolved "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz#8a5bdd664ca4fda4db7997ec634c6e5455b27c44" + integrity sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.5.0" + pg-pool "^3.5.1" + pg-protocol "^1.5.0" + pg-types "^2.1.0" + pgpass "1.x" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -4010,6 +4120,28 @@ pluralize@8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -4024,6 +4156,13 @@ pretty-format@^27.0.0, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" +prisma@^3.14.0: + version "3.14.0" + resolved "https://registry.npmjs.org/prisma/-/prisma-3.14.0.tgz#dd67ece37d7b5373e9fd9588971de0024b49be81" + integrity sha512-l9MOgNCn/paDE+i1K2fp9NZ+Du4trzPTJsGkaQHVBufTGqzoYHuNk8JfzXuIn0Gte6/ZjyKj652Jq/Lc1tp2yw== + dependencies: + "@prisma/engines" "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -4440,6 +4579,11 @@ sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +split2@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" + integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"