diff --git a/jest.config.ts b/jest.config.ts index bb5c5c0..d3e7afd 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -2,7 +2,7 @@ import type { Config } from '@jest/types'; // Jest configuration const config: Config.InitialOptions = { - roots: ['/src', '/src/test'], + roots: ['/src'], transform: { '^.+\\.ts?$': 'ts-jest', }, diff --git a/package-lock.json b/package-lock.json index 5497c39..acd6083 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@initia/rollup", - "version": "0.1.12", + "version": "0.1.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@initia/rollup", - "version": "0.1.12", + "version": "0.1.14", "license": "MIT", "dependencies": { "@koa/cors": "^5.0.0", diff --git a/src/controller/executor/ClaimTxController.ts b/src/controller/executor/ClaimTxController.ts index 19b6c32..43dc4db 100644 --- a/src/controller/executor/ClaimTxController.ts +++ b/src/controller/executor/ClaimTxController.ts @@ -1,11 +1,20 @@ import { Context } from 'koa' -import { KoaController, Get, Controller } from 'koa-joi-controllers' +import { + KoaController, + Get, + Controller, + Validator, + Validate +} from 'koa-joi-controllers' import { ErrorTypes } from '../../lib/error' import { error, success } from '../../lib/response' import { getClaimTxList } from '../../service' import { responses, routeConfig, z } from 'koa-swagger-decorator' import { GetClaimResponse } from '../../swagger/executor_model' import { wrapControllerFunction } from '../../lib/metricsMiddleware' +import { INIT_ACCOUNT_REGEX } from '../../lib/constants' + +const Joi = Validator.Joi @Controller('') export class ClaimTxController extends KoaController { @@ -33,6 +42,18 @@ export class ClaimTxController extends KoaController { } }) @responses(GetClaimResponse) + @Validate({ + query: { + address: Joi.string() + .optional() + .regex(INIT_ACCOUNT_REGEX) + .description('User address'), + sequence: Joi.number().optional(), + limit: Joi.number().optional().default(20), + offset: Joi.number().optional().default(0), + descending: Joi.boolean().optional().default(true) + } + }) @Get('/tx/claim') async getClaimTxList(ctx: Context): Promise { await wrapControllerFunction('get_claim_tx_list', async (ctx) => { diff --git a/src/controller/executor/DepositTxController.ts b/src/controller/executor/DepositTxController.ts index 30b2364..fabbd10 100644 --- a/src/controller/executor/DepositTxController.ts +++ b/src/controller/executor/DepositTxController.ts @@ -1,11 +1,20 @@ import { Context } from 'koa' -import { KoaController, Get, Controller } from 'koa-joi-controllers' +import { + KoaController, + Get, + Controller, + Validator, + Validate +} from 'koa-joi-controllers' import { ErrorTypes } from '../../lib/error' import { error, success } from '../../lib/response' import { getDepositTxList } from '../../service' import { responses, routeConfig, z } from 'koa-swagger-decorator' import { GetDepositResponse } from '../../swagger/executor_model' import { wrapControllerFunction } from '../../lib/metricsMiddleware' +import { INIT_ACCOUNT_REGEX } from '../../lib/constants' + +const Joi = Validator.Joi @Controller('') export class DepositTxController extends KoaController { @@ -33,6 +42,18 @@ export class DepositTxController extends KoaController { } }) @responses(GetDepositResponse) + @Validate({ + query: { + address: Joi.string() + .optional() + .regex(INIT_ACCOUNT_REGEX) + .description('User address'), + sequence: Joi.number().optional(), + limit: Joi.number().optional().default(20), + offset: Joi.number().optional().default(0), + descending: Joi.boolean().optional().default(true) + } + }) @Get('/tx/deposit') async getDepositTxList(ctx: Context): Promise { await wrapControllerFunction('get_deposit_tx_list', async (ctx) => { diff --git a/src/controller/executor/OutputController.ts b/src/controller/executor/OutputController.ts index e671b17..7309c62 100644 --- a/src/controller/executor/OutputController.ts +++ b/src/controller/executor/OutputController.ts @@ -1,5 +1,11 @@ import { Context } from 'koa' -import { KoaController, Get, Controller } from 'koa-joi-controllers' +import { + KoaController, + Get, + Controller, + Validator, + Validate +} from 'koa-joi-controllers' import { ErrorTypes } from '../../lib/error' import { error, success } from '../../lib/response' import { responses, routeConfig, z } from 'koa-swagger-decorator' @@ -7,6 +13,8 @@ import { getOutputList } from '../../service' import { GetOutputResponse } from '../../swagger/executor_model' import { wrapControllerFunction } from '../../lib/metricsMiddleware' +const Joi = Validator.Joi + @Controller('') export class OutputController extends KoaController { @routeConfig({ @@ -32,6 +40,14 @@ export class OutputController extends KoaController { } }) @responses(GetOutputResponse) + @Validate({ + query: { + output_index: Joi.number().optional(), + limit: Joi.number().optional().default(20), + offset: Joi.number().optional().default(0), + descending: Joi.boolean().optional().default(true) + } + }) @Get('/output') async getgetOutputList(ctx: Context): Promise { await wrapControllerFunction('get_output_list', async (ctx) => { diff --git a/src/controller/executor/WithdrawalTxController.ts b/src/controller/executor/WithdrawalTxController.ts index 5e28bf2..688530b 100644 --- a/src/controller/executor/WithdrawalTxController.ts +++ b/src/controller/executor/WithdrawalTxController.ts @@ -1,11 +1,20 @@ import { Context } from 'koa' -import { KoaController, Get, Controller } from 'koa-joi-controllers' +import { + KoaController, + Get, + Controller, + Validator, + Validate +} from 'koa-joi-controllers' import { ErrorTypes } from '../../lib/error' import { error, success } from '../../lib/response' import { getWithdrawalTxList } from '../../service' import { responses, routeConfig, z } from 'koa-swagger-decorator' import { GetWithdrawalResponse } from '../../swagger/executor_model' import { wrapControllerFunction } from '../../lib/metricsMiddleware' +import { INIT_ACCOUNT_REGEX } from '../../lib/constants' + +const Joi = Validator.Joi @Controller('') export class WithdrawalTxController extends KoaController { @@ -33,6 +42,18 @@ export class WithdrawalTxController extends KoaController { } }) @responses(GetWithdrawalResponse) + @Validate({ + query: { + address: Joi.string() + .optional() + .regex(INIT_ACCOUNT_REGEX) + .description('User address'), + sequence: Joi.number().optional(), + limit: Joi.number().optional().default(20), + offset: Joi.number().optional().default(0), + descending: Joi.boolean().optional().default(true) + } + }) @Get('/tx/withdrawal') async getWithdrawalTxList(ctx: Context): Promise { await wrapControllerFunction('get_withdrawal_tx_list', async (ctx) => { diff --git a/src/controller/metrics/MetricsController.ts b/src/controller/metrics/MetricsController.ts index 4323958..7b24eaf 100644 --- a/src/controller/metrics/MetricsController.ts +++ b/src/controller/metrics/MetricsController.ts @@ -1,15 +1,9 @@ import { Context } from 'koa' import { KoaController, Get, Controller } from 'koa-joi-controllers' -import { routeConfig } from 'koa-swagger-decorator' import { Prometheus } from '../../lib/metrics' @Controller('') export class MetricsController extends KoaController { - @routeConfig({ - method: 'get', - path: '/metrics', - tags: ['Metrics'] - }) @Get('/metrics') async getMetrics(ctx: Context): Promise { try { diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..de619e4 --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,5 @@ +export const INIT_BECH32_REGEX = /^init1(?:[a-z0-9]){38}/ +export const INIT_HEX_REGEX = /0x(?:[a-f0-9]*){1,64}/ +export const INIT_ACCOUNT_REGEX = new RegExp( + INIT_BECH32_REGEX.source + '|' + INIT_HEX_REGEX.source +) diff --git a/src/lib/storage.spec.ts b/src/lib/storage.spec.ts index 5558cae..5af4b82 100644 --- a/src/lib/storage.spec.ts +++ b/src/lib/storage.spec.ts @@ -25,10 +25,70 @@ const v1 = [ receiver: 'init174knscjg688ddtxj8smyjz073r3w5mmsp3m0m2', l1_denom: 'uinit', amount: BigInt(1000000) + }, + { + bridge_id: BigInt(1), + sequence: BigInt(4), + sender: 'init1wzenw7r2t2ra39k4l9yqq95pw55ap4sm4vsa9g', + receiver: 'init174knscjg688ddtxj8smyjz073r3w5mmsp3m0m2', + l1_denom: 'uinit', + amount: BigInt(1000231200) + }, + { + bridge_id: BigInt(1), + sequence: BigInt(5), + sender: 'init1wzenw7r2t2ra39k4l9yqq95pw55ap4sm4vsa9g', + receiver: 'init174knscjg688ddtxj8smyjz073r3w5mmsp3m0m2', + l1_denom: 'uinit', + amount: BigInt(32340000) + }, + { + bridge_id: BigInt(1), + sequence: BigInt(6), + sender: 'init1wzenw7r2t2ra39k4l9yqq95pw55ap4sm4vsa9g', + receiver: 'init174knscjg688ddtxj8smyjz073r3w5mmsp3m0m2', + l1_denom: 'uinit', + amount: BigInt(101230000) } ] describe('WithdrawStorage', () => { + it('getmerkleproof', async () => { + const tx = { + bridge_id: BigInt(1), + sequence: BigInt(4), + sender: '0000000000000000000000000000000000000004', + receiver: '0000000000000000000000000000000000000001', + l1_denom: 'l1denom', + amount: BigInt(3000000) + } + const bridge_id_buf = Buffer.alloc(8) + bridge_id_buf.writeBigInt64BE(tx.bridge_id) + + const sequence_buf = Buffer.alloc(8) + sequence_buf.writeBigInt64BE(tx.sequence) + + const amount_buf = Buffer.alloc(8) + amount_buf.writeBigInt64BE(tx.amount) + + const result = sha3_256( + Buffer.concat([ + bridge_id_buf, + sequence_buf, + Buffer.from(tx.sender, 'hex'), + Buffer.from('|'), + Buffer.from(tx.receiver, 'hex'), + Buffer.from('|'), + Buffer.from(tx.l1_denom, 'utf8'), + Buffer.from('|'), + amount_buf + ]) + ).toString('base64') + expect( + result == 'F+mzhRVdcwLS5tk2NDB2MbgMm7A0nk39G+NGEjXpTV0=' + ).toBeTruthy() + }) + it('verify v1', async () => { const airdrop = new WithdrawStorage(v1) const target = v1[0] @@ -47,11 +107,12 @@ describe('WithdrawStorage', () => { ]) ).toString('base64') expect(airdrop.verify(merkleProof, target)).toBeTruthy() - - expect(merkleRoot).toEqual('EYgpXs1b+Z3AdGqjjtJHylrGzCjXtBKDD2UTPXelUk4=') + expect(merkleRoot).toEqual('VcN+0UZbTtGyyLfQtAHW+bCv5ixadyyT0ZZ26aUT1JY=') expect(merkleProof).toEqual([ - '5eJNy8mEqvyhysgWCqi7JQ7K602FtSpz+wDRNQitQMc=' + 'gnUeNU3EnW4iBOk8wounvu98aTER0BP5dOD0lkuwBBE=', + 'yE4zjliK5P9sfdzR2iNh6nYHmD+mjDK6dONuZ3QlVcA=', + 'GQXXUQ5P/egGvbAHkYfWHIAfgyCEmnjz/fUMKrWCEn8=' ]) - expect(outputRoot).toEqual('euaoJcFRXfV/6F0AiC0vYwXUY4NPHfCn9LbFMPieNsA=') + expect(outputRoot).toEqual('0cg24XcpDwTIFXHY4jNyxg2EQS5RUqcMvlMJeuI5rf4=') }) }) diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 03a1c11..cc13524 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -3,6 +3,8 @@ import { sha3_256 } from './util' import { WithdrawalTx } from './types' import { AccAddress } from 'initia-l1' +const SPLITTER = Buffer.from('|', 'utf8') + function convertHexToBase64(hex: string): string { return Buffer.from(hex, 'hex').toString('base64') } @@ -26,8 +28,11 @@ export class WithdrawStorage { bridge_id_buf, sequence_buf, AccAddress.toBuffer(tx.sender), + SPLITTER, AccAddress.toBuffer(tx.receiver), + SPLITTER, Buffer.from(tx.l1_denom, 'utf8'), + SPLITTER, amount_buf ]) ) @@ -57,8 +62,11 @@ export class WithdrawStorage { bridge_id_buf, sequence_buf, AccAddress.toBuffer(tx.sender), + SPLITTER, AccAddress.toBuffer(tx.receiver), + SPLITTER, Buffer.from(tx.l1_denom, 'utf8'), + SPLITTER, amount_buf ]) ) @@ -91,8 +99,11 @@ export class WithdrawStorage { bridge_id_buf, sequence_buf, AccAddress.toBuffer(tx.sender), + SPLITTER, AccAddress.toBuffer(tx.receiver), + SPLITTER, Buffer.from(tx.l1_denom, 'utf8'), + SPLITTER, amount_buf ]) ) diff --git a/src/orm/index.ts b/src/orm/index.ts index c494e04..7de4793 100644 --- a/src/orm/index.ts +++ b/src/orm/index.ts @@ -8,8 +8,6 @@ import ExecutorUnconfirmedTxEntity from './executor/UnconfirmedTxEntity' import RecordEntity from './batch/RecordEntity' import BatchTxEntity from './batch/BatchTxEntity' -import OutputTxEntity from './output/OutputTxEntity' - import ChallengerDepositTxEntity from './challenger/DepositTxEntity' import ChallengerWithdrawalTxEntity from './challenger/WithdrawalTxEntity' import ChallengerFinalizeDepositTxEntity from './challenger/FinalizeDepositTxEntity' @@ -18,7 +16,6 @@ import ChallengerOutputEntity from './challenger/OutputEntity' import ChallengedOutputEntity from './challenger/DeletedOutputEntity' import ChallengeEntity from './challenger/ChallengeEntity' -export * from './output/OutputTxEntity' export * from './batch/RecordEntity' export * from './batch/BatchTxEntity' @@ -40,7 +37,6 @@ export * from './challenger/ChallengeEntity' export { RecordEntity, BatchTxEntity, - OutputTxEntity, StateEntity, ExecutorWithdrawalTxEntity, ExecutorDepositTxEntity, diff --git a/src/orm/output/OutputTxEntity.ts b/src/orm/output/OutputTxEntity.ts deleted file mode 100644 index 0ac6712..0000000 --- a/src/orm/output/OutputTxEntity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Column, Entity, Index, PrimaryColumn } from 'typeorm' - -@Entity('output_tx') -export default class OutputTxEntity { - @PrimaryColumn('text') - txHash: string - - @Column() - @Index('output_tx_output_index_index') - outputIndex: number - - @Column() - processed: boolean -} diff --git a/src/service/executor/ClaimTxService.ts b/src/service/executor/ClaimTxService.ts index 1a66971..8e67b91 100644 --- a/src/service/executor/ClaimTxService.ts +++ b/src/service/executor/ClaimTxService.ts @@ -37,79 +37,70 @@ export async function getClaimTxList( param: GetClaimTxListParam ): Promise { const [db] = getDB() - const queryRunner = db.createQueryRunner('slave') - try { - const offset = param.offset ?? 0 - const order = param.descending == 'true' ? 'DESC' : 'ASC' - const limit = Number(param.limit) ?? 10 + const offset = param.offset ?? 0 + const order = param.descending ? 'DESC' : 'ASC' + const limit = Number(param.limit) ?? 20 - const claimTxList: ClaimTx[] = [] + const claimTxList: ClaimTx[] = [] - const withdrawalQb = queryRunner.manager.createQueryBuilder( - ExecutorWithdrawalTxEntity, - 'tx' - ) + const withdrawalRepo = db.getRepository(ExecutorWithdrawalTxEntity) + const withdrawalWhereCond = {} - if (param.address) { - withdrawalQb.andWhere('tx.sender = :sender', { sender: param.address }) - } + if (param.address) { + withdrawalWhereCond['receiver'] = param.address + } - if (param.sequence) { - withdrawalQb.andWhere('tx.sequence = :sequence', { - sequence: param.sequence - }) - } + if (param.sequence) { + withdrawalWhereCond['sequence'] = param.sequence + } - const withdrawalTxs = await withdrawalQb - .orderBy('tx.sequence', order) - .skip(offset * limit) - .take(limit) - .getMany() - - withdrawalTxs.map(async (withdrawalTx) => { - const outputQb = queryRunner.manager - .createQueryBuilder(ExecutorOutputEntity, 'output') - .where('output.output_index = :outputIndex', { - outputIndex: withdrawalTx.outputIndex - }) - - const output = await outputQb.getOne() - - if (!output) { - throw new APIError(ErrorTypes.NOT_FOUND_ERROR) - } - - const claimData: ClaimTx = { - bridgeId: parseInt(withdrawalTx.bridgeId), - outputIndex: withdrawalTx.outputIndex, - merkleProof: withdrawalTx.merkleProof, - sender: withdrawalTx.sender, - receiver: withdrawalTx.receiver, - amount: parseInt(withdrawalTx.amount), - l2Denom: withdrawalTx.l2Denom, - version: sha3_256(withdrawalTx.outputIndex).toString('base64'), - stateRoot: output.stateRoot, - merkleRoot: output.merkleRoot, - lastBlockHash: output.lastBlockHash - } - claimTxList.push(claimData) + const withdrawalTxs = await withdrawalRepo.find({ + where: withdrawalWhereCond, + order: { + sequence: order + }, + skip: offset * limit, + take: limit + }) + + withdrawalTxs.map(async (withdrawalTx) => { + const output = await db.getRepository(ExecutorOutputEntity).findOne({ + where: { outputIndex: withdrawalTx.outputIndex } }) - const count = await withdrawalQb.getCount() - let next: number | undefined - - if (count > (offset + 1) * limit) { - next = offset + 1 + if (!output) { + throw new APIError(ErrorTypes.NOT_FOUND_ERROR) } - return { - count, - next, - limit, - claimTxList + const claimData: ClaimTx = { + bridgeId: parseInt(withdrawalTx.bridgeId), + outputIndex: withdrawalTx.outputIndex, + merkleProof: withdrawalTx.merkleProof, + sender: withdrawalTx.sender, + receiver: withdrawalTx.receiver, + amount: parseInt(withdrawalTx.amount), + l2Denom: withdrawalTx.l2Denom, + version: sha3_256(withdrawalTx.outputIndex).toString('base64'), + stateRoot: output.stateRoot, + merkleRoot: output.merkleRoot, + lastBlockHash: output.lastBlockHash } - } finally { - await queryRunner.release() + claimTxList.push(claimData) + }) + + const count = withdrawalTxs.length + + let next: number | undefined + + if (count > (offset + 1) * limit) { + next = offset + 1 + } + + return { + count, + next, + limit, + claimTxList } } diff --git a/src/service/executor/DepositTxService.ts b/src/service/executor/DepositTxService.ts index 9ab3059..21b7eb0 100644 --- a/src/service/executor/DepositTxService.ts +++ b/src/service/executor/DepositTxService.ts @@ -20,45 +20,42 @@ export async function getDepositTxList( param: GetDepositTxListParam ): Promise { const [db] = getDB() - const queryRunner = db.createQueryRunner('slave') - try { - const offset = param.offset ?? 0 - const order = param.descending == 'true' ? 'DESC' : 'ASC' - const limit = Number(param.limit) ?? 10 + const offset = param.offset ?? 0 + const order = param.descending ? 'DESC' : 'ASC' + const limit = Number(param.limit) ?? 20 - const qb = queryRunner.manager.createQueryBuilder( - ExecutorDepositTxEntity, - 'tx' - ) + const depositTxRepo = db.getRepository(ExecutorDepositTxEntity) + const depositTxWhereCond = {} - if (param.sequence) { - qb.andWhere('tx.sequence = :sequence', { sequence: param.sequence }) - } + if (param.sequence) { + depositTxWhereCond['sequence'] = param.sequence + } + + if (param.address) { + depositTxWhereCond['sender'] = param.address + } - if (param.address) { - qb.andWhere('tx.sender = :sender', { sender: param.address }) - } + const depositTxList = await depositTxRepo.find({ + where: depositTxWhereCond, + order: { + sequence: order + }, + skip: offset * limit, + take: limit + }) - const depositTxList = await qb - .orderBy('tx.sequence', order) - .skip(offset * limit) - .take(limit) - .getMany() + const count = depositTxList.length - const count = await qb.getCount() - let next: number | undefined + let next: number | undefined - if (count > (offset + 1) * limit) { - next = offset + 1 - } + if (count > (offset + 1) * limit) { + next = offset + 1 + } - return { - count, - next, - limit, - depositTxList - } - } finally { - queryRunner.release() + return { + count, + next, + limit, + depositTxList } } diff --git a/src/service/executor/OutputService.ts b/src/service/executor/OutputService.ts index c2d2d0d..e80293f 100644 --- a/src/service/executor/OutputService.ts +++ b/src/service/executor/OutputService.ts @@ -6,7 +6,7 @@ export interface GetOutputListParam { height?: number; offset?: number; limit: number; - descending: string; + descending: boolean; } export interface GetOutputListResponse { @@ -20,43 +20,37 @@ export async function getOutputList( param: GetOutputListParam ): Promise { const [db] = getDB() - const queryRunner = db.createQueryRunner('slave') - try { - const offset = param.offset ?? 0 - const order = param.descending == 'true' ? 'DESC' : 'ASC' - const limit = Number(param.limit) ?? 10 - - const qb = queryRunner.manager.createQueryBuilder( - ExecutorOutputEntity, - 'output' - ) - - if (param.output_index) { - qb.andWhere('output.output_index = :output_index', { - output_index: param.output_index - }) - } - - const outputList = await qb - .orderBy('output.output_index', order) - .skip(offset * limit) - .take(limit) - .getMany() - - const count = await qb.getCount() - let next: number | undefined - - if (count > (offset + 1) * limit) { - next = offset + 1 - } - - return { - count, - next, - limit, - outputList - } - } finally { - queryRunner.release() + const offset = param.offset ?? 0 + const order = param.descending ? 'DESC' : 'ASC' + const limit = Number(param.limit) ?? 20 + + const outputRepo = db.getRepository(ExecutorOutputEntity) + const outputWhereCond = {} + + if (param.output_index) { + outputWhereCond['outputIndex'] = param.output_index + } + + const outputList = await outputRepo.find({ + where: outputWhereCond, + order: { + outputIndex: order + }, + skip: offset * limit, + take: limit + }) + + const count = outputList.length + let next: number | undefined + + if (count > (offset + 1) * limit) { + next = offset + 1 + } + + return { + count, + next, + limit, + outputList } } diff --git a/src/service/executor/WithdrawalTxService.ts b/src/service/executor/WithdrawalTxService.ts index e1ef235..83f4d28 100644 --- a/src/service/executor/WithdrawalTxService.ts +++ b/src/service/executor/WithdrawalTxService.ts @@ -20,45 +20,42 @@ export async function getWithdrawalTxList( param: GetWithdrawalTxListParam ): Promise { const [db] = getDB() - const queryRunner = db.createQueryRunner('slave') - try { - const offset = param.offset ?? 0 - const order = param.descending == 'true' ? 'DESC' : 'ASC' - const limit = Number(param.limit) ?? 10 + const offset = param.offset ?? 0 + const order = param.descending ? 'DESC' : 'ASC' + const limit = Number(param.limit) ?? 20 - const qb = queryRunner.manager.createQueryBuilder( - ExecutorWithdrawalTxEntity, - 'tx' - ) + const withdrawalRepo = db.getRepository(ExecutorWithdrawalTxEntity) + const withdrawalWhereCond = {} - if (param.sequence) { - qb.andWhere('tx.sequence = :sequence', { sequence: param.sequence }) - } + if (param.sequence) { + withdrawalWhereCond['sequence'] = param.sequence + } + + if (param.address) { + withdrawalWhereCond['receiver'] = param.address + } - if (param.address) { - qb.andWhere('tx.sender = :sender', { sender: param.address }) - } + const withdrawalTxList = await withdrawalRepo.find({ + where: withdrawalWhereCond, + order: { + sequence: order + }, + skip: offset * limit, + take: limit + }) - const withdrawalTxList = await qb - .orderBy('tx.sequence', order) - .skip(offset * limit) - .take(limit) - .getMany() + const count = withdrawalTxList.length - const count = await qb.getCount() - let next: number | undefined + let next: number | undefined - if (count > (offset + 1) * param.limit) { - next = offset + 1 - } + if (count > (offset + 1) * param.limit) { + next = offset + 1 + } - return { - count, - next, - limit: param.limit, - withdrawalTxList - } - } finally { - queryRunner.release() + return { + count, + next, + limit: param.limit, + withdrawalTxList } } diff --git a/src/swagger/swagger.ts b/src/swagger/swagger.ts index 1b6d634..1748c46 100644 --- a/src/swagger/swagger.ts +++ b/src/swagger/swagger.ts @@ -7,7 +7,7 @@ import { ClaimTxController } from '../controller/executor/ClaimTxController' const router = new SwaggerRouter({ spec: { info: { - title: 'Initia VIP API', + title: 'Initia OPinit API', version: 'v1.0' } }, diff --git a/src/worker/bridgeExecutor/monitor/helper.ts b/src/worker/bridgeExecutor/monitor/helper.ts index b8d49b3..8456369 100644 --- a/src/worker/bridgeExecutor/monitor/helper.ts +++ b/src/worker/bridgeExecutor/monitor/helper.ts @@ -12,7 +12,12 @@ import { WithdrawStorage } from '../../../lib/storage' import { WithdrawalTx } from '../../../lib/types' import { sha3_256 } from '../../../lib/util' import OutputEntity from '../../../orm/executor/OutputEntity' -import { EntityManager, EntityTarget, ObjectLiteral } from 'typeorm' +import { + EntityManager, + EntityTarget, + MoreThanOrEqual, + ObjectLiteral +} from 'typeorm' import { Block, BlockResults, RPCClient } from '../../../lib/rpc' class MonitorHelper { @@ -91,6 +96,19 @@ class MonitorHelper { }) } + public async getAllOutput( + manager: EntityManager, + entityClass: EntityTarget, + startIndex = 0 + ): Promise { + return await manager.getRepository(entityClass).find({ + where: { + outputIndex: MoreThanOrEqual(startIndex) as any + }, + order: { outputIndex: 'ASC' } as any + }) + } + public async saveEntity( manager: EntityManager, entityClass: EntityTarget, diff --git a/src/worker/outputSubmitter/db.ts b/src/worker/outputSubmitter/db.ts index 39a4301..8563735 100644 --- a/src/worker/outputSubmitter/db.ts +++ b/src/worker/outputSubmitter/db.ts @@ -11,12 +11,12 @@ import dbg from 'debug' const debug = dbg('orm') -import { ExecutorOutputEntity, OutputTxEntity } from '../../orm' +import { ExecutorOutputEntity } from '../../orm' const staticOptions = { supportBigNumbers: true, bigNumberStrings: true, - entities: [ExecutorOutputEntity, OutputTxEntity] + entities: [ExecutorOutputEntity] } let DB: DataSource[] = [] diff --git a/src/worker/outputSubmitter/index.ts b/src/worker/outputSubmitter/index.ts index 0a8f0b2..9434084 100644 --- a/src/worker/outputSubmitter/index.ts +++ b/src/worker/outputSubmitter/index.ts @@ -3,8 +3,8 @@ import { outputLogger as logger } from '../../lib/logger' import { once } from 'lodash' import { initORM } from './db' import { initMetricsServer } from '../../loader' -import { metricsController } from '../../controller' import { config, isInvokedFromEntrypoint } from '../../config' +import { metricsController } from '../../controller' let jobs: OutputSubmitter[] diff --git a/src/worker/outputSubmitter/outputSubmitter.ts b/src/worker/outputSubmitter/outputSubmitter.ts index 522f92c..2291509 100644 --- a/src/worker/outputSubmitter/outputSubmitter.ts +++ b/src/worker/outputSubmitter/outputSubmitter.ts @@ -1,4 +1,4 @@ -import { MsgProposeOutput } from 'initia-l1' +import { Msg, MsgProposeOutput } from 'initia-l1' import { INTERVAL_OUTPUT } from '../../config' import { ExecutorOutputEntity } from '../../orm' import { delay } from 'bluebird' @@ -17,6 +17,8 @@ import { } from '../../lib/walletL1' import { updateOutputUsageMetrics } from '../../lib/metrics' +const MAX_OUTPUT_PROPOSAL = 50 + export class OutputSubmitter { private db: DataSource private submitter: TxWalletL1 @@ -44,31 +46,29 @@ export class OutputSubmitter { } } - async getOutput( - manager: EntityManager - ): Promise { + async getOutputs(manager: EntityManager): Promise { try { const lastOutputInfo = await getLastOutputInfo(this.bridgeId) if (lastOutputInfo) { this.syncedOutputIndex = lastOutputInfo.output_index + 1 + this.processedBlockNumber = + lastOutputInfo.output_proposal.l2_block_number } - const output = await this.helper.getOutputByIndex( + const outputs = await this.helper.getAllOutput( manager, ExecutorOutputEntity, this.syncedOutputIndex ) - if (!output) return null - - return output + return outputs } catch (err) { if (err.response?.data.type === ErrorTypes.NOT_FOUND_ERROR) { logger.warn( `waiting for output index from L1: ${this.syncedOutputIndex}, processed block number: ${this.processedBlockNumber}` ) await delay(INTERVAL_OUTPUT) - return null + return [] } throw err } @@ -76,18 +76,21 @@ export class OutputSubmitter { async processOutput() { await this.db.transaction(async (manager: EntityManager) => { - const output = await this.getOutput(manager) - if (!output) { + const outputs = await this.getOutputs(manager) + if (outputs.length === 0) { logger.info( `waiting for output index from DB: ${this.syncedOutputIndex}, processed block number: ${this.processedBlockNumber}` ) return } - await this.proposeOutput(output) - logger.info( - `successfully submitted! output index: ${this.syncedOutputIndex}, output root: ${output.outputRoot} (${output.startBlockNumber}, ${output.endBlockNumber})` - ) + const chunkedOutputs: ExecutorOutputEntity[] = [] + + for (let i = 0; i < outputs.length; i += MAX_OUTPUT_PROPOSAL) { + chunkedOutputs.push(...outputs.slice(i, i + MAX_OUTPUT_PROPOSAL)) + await this.proposeOutputs(chunkedOutputs) + chunkedOutputs.length = 0 + } }) } @@ -95,16 +98,25 @@ export class OutputSubmitter { this.isRunning = false } - private async proposeOutput(outputEntity: ExecutorOutputEntity) { - const msg = new MsgProposeOutput( - this.submitter.key.accAddress, - this.bridgeId, - outputEntity.endBlockNumber, - outputEntity.outputRoot - ) + private async proposeOutputs(outputEntities: ExecutorOutputEntity[]) { + const msgs: Msg[] = [] - await this.submitter.transaction([msg], undefined, 1000 * 60 * 10) // 10 minutes + for (const output of outputEntities) { + msgs.push( + new MsgProposeOutput( + this.submitter.key.accAddress, + this.bridgeId, + output.endBlockNumber, + output.outputRoot + ) + ) + } - this.processedBlockNumber = outputEntity.endBlockNumber + await this.submitter.transaction(msgs, undefined, 1000 * 60 * 10) // 10 minutes + this.processedBlockNumber = + outputEntities[outputEntities.length - 1].endBlockNumber + logger.info( + `succeed to propose ${outputEntities.length} outputs from ${outputEntities[0].outputIndex} to ${outputEntities[outputEntities.length - 1].outputIndex}, processed block number ${this.processedBlockNumber}` + ) } }