From 494fc98c66859e7348e620222969d4618d430cc2 Mon Sep 17 00:00:00 2001 From: Roman Petriv Date: Thu, 12 Oct 2023 12:31:50 +0300 Subject: [PATCH] feat: add validated blocks by address api (#44) --- .../api/src/address/address.controller.ts | 2 +- .../api/account/account.controller.spec.ts | 39 +++++++++ .../api/src/api/account/account.controller.ts | 30 +++++++ packages/api/src/api/api.controller.spec.ts | 7 ++ packages/api/src/api/api.controller.ts | 23 ++++++ .../api/dtos/account/accountMinedBlock.dto.ts | 24 ++++++ .../account/accountMinedBlocksResponse.dto.ts | 12 +++ .../api/src/api/log/log.controller.spec.ts | 8 +- packages/api/src/api/log/log.controller.ts | 2 +- packages/api/src/api/types.ts | 1 + packages/api/src/batch/batch.controller.ts | 2 +- packages/api/src/block/block.controller.ts | 2 +- packages/api/src/block/block.service.spec.ts | 79 ++++++++++++++++++- packages/api/src/block/block.service.ts | 21 +++++ packages/api/src/log/log.service.spec.ts | 20 ++--- packages/api/src/log/log.service.ts | 2 +- packages/api/src/stats/stats.controller.ts | 2 +- packages/api/src/token/token.controller.ts | 2 +- .../src/transaction/transaction.controller.ts | 2 +- packages/api/test/account-api.e2e-spec.ts | 21 +++++ packages/worker/src/entities/block.entity.ts | 1 + .../1696897107720-AddBlockMinerIndex.ts | 13 +++ 22 files changed, 292 insertions(+), 23 deletions(-) create mode 100644 packages/api/src/api/dtos/account/accountMinedBlock.dto.ts create mode 100644 packages/api/src/api/dtos/account/accountMinedBlocksResponse.dto.ts create mode 100644 packages/worker/src/migrations/1696897107720-AddBlockMinerIndex.ts diff --git a/packages/api/src/address/address.controller.ts b/packages/api/src/address/address.controller.ts index 5c1498c089..6ed0b89b6b 100644 --- a/packages/api/src/address/address.controller.ts +++ b/packages/api/src/address/address.controller.ts @@ -19,7 +19,7 @@ import { TransferDto } from "../transfer/transfer.dto"; const entityName = "address"; -@ApiTags(entityName) +@ApiTags("Address BFF") @Controller(entityName) export class AddressController { constructor( diff --git a/packages/api/src/api/account/account.controller.spec.ts b/packages/api/src/api/account/account.controller.spec.ts index 921bf8e54e..73241b7bdd 100644 --- a/packages/api/src/api/account/account.controller.spec.ts +++ b/packages/api/src/api/account/account.controller.spec.ts @@ -3,6 +3,7 @@ import { mock } from "jest-mock-extended"; import { BadRequestException, Logger } from "@nestjs/common"; import { L2_ETH_TOKEN_ADDRESS } from "../../common/constants"; import { BlockService } from "../../block/block.service"; +import { BlockDetail } from "../../block/blockDetail.entity"; import { TransactionService } from "../../transaction/transaction.service"; import { BalanceService } from "../../balance/balance.service"; import { TransactionStatus } from "../../transaction/entities/transaction.entity"; @@ -104,6 +105,7 @@ describe("AccountController", () => { beforeEach(async () => { blockServiceMock = mock({ getLastBlockNumber: jest.fn().mockResolvedValue(100), + findMany: jest.fn().mockResolvedValue([]), }); transactionServiceMock = mock({ findByAddress: jest.fn().mockResolvedValue([]), @@ -603,4 +605,41 @@ describe("AccountController", () => { expect(parseAddressListPipeExceptionFactory()).toEqual(new BadRequestException("Error! Missing address")); }); }); + + describe("getAccountMinedBlocks", () => { + it("returns not ok response when no blocks by miner found", async () => { + const response = await controller.getAccountMinedBlocks(address, { + page: 1, + offset: 10, + maxLimit: 100, + }); + expect(response).toEqual({ + status: ResponseStatus.NOTOK, + message: ResponseMessage.NO_TRANSACTIONS_FOUND, + result: [], + }); + }); + + it("returns blocks list response when block by miner are found", async () => { + jest + .spyOn(blockServiceMock, "findMany") + .mockResolvedValue([{ number: 1, timestamp: new Date("2023-03-03") } as BlockDetail]); + const response = await controller.getAccountMinedBlocks(address, { + page: 1, + offset: 10, + maxLimit: 100, + }); + expect(response).toEqual({ + status: ResponseStatus.OK, + message: ResponseMessage.OK, + result: [ + { + blockNumber: "1", + timeStamp: "1677801600", + blockReward: "0", + }, + ], + }); + }); + }); }); diff --git a/packages/api/src/api/account/account.controller.ts b/packages/api/src/api/account/account.controller.ts index 507875b9c9..5299fa44b6 100644 --- a/packages/api/src/api/account/account.controller.ts +++ b/packages/api/src/api/account/account.controller.ts @@ -2,6 +2,7 @@ import { Controller, Get, Query, Logger, UseFilters, ParseArrayPipe, BadRequestE import { ApiTags, ApiExcludeController } from "@nestjs/swagger"; import { L2_ETH_TOKEN_ADDRESS } from "../../common/constants"; import { TokenType } from "../../token/token.entity"; +import { dateToTimestamp } from "../../common/utils"; import { BlockService } from "../../block/block.service"; import { TransactionService } from "../../transaction/transaction.service"; import { TransferService } from "../../transfer/transfer.service"; @@ -25,6 +26,7 @@ import { AccountEtherBalanceResponseDto, AccountsEtherBalancesResponseDto, } from "../dtos/account/accountEtherBalanceResponse.dto"; +import { AccountMinedBlocksResponseDto } from "../dtos/account/accountMinedBlocksResponse.dto"; import { ApiExceptionFilter } from "../exceptionFilter"; const entityName = "account"; @@ -225,4 +227,32 @@ export class AccountController { result: balance, }; } + + @Get("/getminedblocks") + public async getAccountMinedBlocks( + @Query("address", new ParseAddressPipe()) address: string, + @Query() pagingOptions: PagingOptionsWithMaxItemsLimitDto + ): Promise { + const blocks = await this.blockService.findMany({ + miner: address, + ...pagingOptions, + selectFields: ["number", "timestamp"], + }); + if (!blocks.length) { + return { + status: ResponseStatus.NOTOK, + message: ResponseMessage.NO_TRANSACTIONS_FOUND, + result: [], + }; + } + return { + status: ResponseStatus.OK, + message: ResponseMessage.OK, + result: blocks.map((block) => ({ + blockNumber: block.number.toString(), + timeStamp: dateToTimestamp(block.timestamp).toString(), + blockReward: "0", + })), + }; + } } diff --git a/packages/api/src/api/api.controller.spec.ts b/packages/api/src/api/api.controller.spec.ts index bf6968c6c4..7264b80867 100644 --- a/packages/api/src/api/api.controller.spec.ts +++ b/packages/api/src/api/api.controller.spec.ts @@ -146,6 +146,13 @@ describe("ApiController", () => { }); }); + describe("getAccountMinedBlocks", () => { + it("returns null as it is defined only to appear in docs and cannot be called", async () => { + const result = await controller.getAccountMinedBlocks({ page: 1, offset: 10, maxLimit: 1000 }); + expect(result).toBe(null); + }); + }); + describe("getBlockNumberByTimestamp", () => { it("returns null as it is defined only to appear in docs and cannot be called", async () => { const result = await controller.getBlockNumberByTimestamp(); diff --git a/packages/api/src/api/api.controller.ts b/packages/api/src/api/api.controller.ts index 99932064fb..db8ca20ee8 100644 --- a/packages/api/src/api/api.controller.ts +++ b/packages/api/src/api/api.controller.ts @@ -22,6 +22,8 @@ import { AccountsEtherBalancesResponseDto, } from "./dtos/account/accountEtherBalanceResponse.dto"; import { AccountTokenBalanceResponseDto } from "./dtos/account/accountTokenBalanceResponse.dto"; +import { AccountMinedBlock } from "./dtos/account/accountMinedBlock.dto"; +import { AccountMinedBlocksResponseDto } from "./dtos/account/accountMinedBlocksResponse.dto"; import { BlockNumberResponseDto } from "./dtos/block/blockNumberResponse.dto"; import { BlockCountdownResponseDto } from "./dtos/block/blockCountdownResponse.dto"; import { BlockRewardResponseDto } from "./dtos/block/blockRewardResponse.dto"; @@ -364,6 +366,27 @@ export class ApiController { return null; } + @ApiTags("Account API") + @Get("api?module=account&action=getminedblocks") + @ApiOperation({ summary: "Get list of Blocks Validated by Address" }) + @ApiQuery({ + name: "address", + description: "The address to get validated blocks by", + example: "0x0000000000000000000000000000000000000000", + required: true, + }) + @ApiExtraModels(AccountMinedBlock) + @ApiOkResponse({ + description: "Blocks validated by address", + type: AccountMinedBlocksResponseDto, + }) + public async getAccountMinedBlocks( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + @Query() pagingOptions: PagingOptionsWithMaxItemsLimitDto + ): Promise { + return null; + } + @ApiTags("Block API") @Get("api?module=block&action=getblocknobytime") @ApiOperation({ summary: "Retrieve block number closest to a specific timestamp" }) diff --git a/packages/api/src/api/dtos/account/accountMinedBlock.dto.ts b/packages/api/src/api/dtos/account/accountMinedBlock.dto.ts new file mode 100644 index 0000000000..ea2ed4cd0c --- /dev/null +++ b/packages/api/src/api/dtos/account/accountMinedBlock.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from "@nestjs/swagger"; + +export class AccountMinedBlock { + @ApiProperty({ + type: String, + description: "The number (height) of the block", + example: "3233097", + }) + public readonly blockNumber: string; + + @ApiProperty({ + type: String, + description: "The timestamp of the block", + example: "1679988122", + }) + public readonly timeStamp: string; + + @ApiProperty({ + type: String, + description: "Reward for the block", + example: "1000", + }) + public readonly blockReward: string; +} diff --git a/packages/api/src/api/dtos/account/accountMinedBlocksResponse.dto.ts b/packages/api/src/api/dtos/account/accountMinedBlocksResponse.dto.ts new file mode 100644 index 0000000000..e4f8d4c5de --- /dev/null +++ b/packages/api/src/api/dtos/account/accountMinedBlocksResponse.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { ResponseBaseDto } from "../common/responseBase.dto"; +import { AccountMinedBlock } from "./accountMinedBlock.dto"; + +export class AccountMinedBlocksResponseDto extends ResponseBaseDto { + @ApiProperty({ + description: "List of blocks validated by address", + type: AccountMinedBlock, + isArray: true, + }) + public readonly result: AccountMinedBlock[]; +} diff --git a/packages/api/src/api/log/log.controller.spec.ts b/packages/api/src/api/log/log.controller.spec.ts index 2d00756b71..6a9b16fb33 100644 --- a/packages/api/src/api/log/log.controller.spec.ts +++ b/packages/api/src/api/log/log.controller.spec.ts @@ -16,7 +16,7 @@ describe("LogController", () => { const address = "address"; beforeEach(async () => { logServiceMock = mock({ - findLogs: jest.fn().mockResolvedValue([ + findMany: jest.fn().mockResolvedValue([ { logIndex: 1, }, @@ -52,8 +52,8 @@ describe("LogController", () => { 0, 10 ); - expect(logServiceMock.findLogs).toBeCalledTimes(1); - expect(logServiceMock.findLogs).toBeCalledWith({ + expect(logServiceMock.findMany).toBeCalledTimes(1); + expect(logServiceMock.findMany).toBeCalledWith({ address, fromBlock: 0, toBlock: 10, @@ -89,7 +89,7 @@ describe("LogController", () => { }); it("returns not ok response and empty logs list when logs are not found", async () => { - (logServiceMock.findLogs as jest.Mock).mockResolvedValueOnce([]); + (logServiceMock.findMany as jest.Mock).mockResolvedValueOnce([]); const response = await controller.getLogs( address, { diff --git a/packages/api/src/api/log/log.controller.ts b/packages/api/src/api/log/log.controller.ts index f68fc6692d..9a138c3836 100644 --- a/packages/api/src/api/log/log.controller.ts +++ b/packages/api/src/api/log/log.controller.ts @@ -26,7 +26,7 @@ export class LogController { @Query("fromBlock", new ParseLimitedIntPipe({ min: 0, isOptional: true })) fromBlock?: number, @Query("toBlock", new ParseLimitedIntPipe({ min: 0, isOptional: true })) toBlock?: number ): Promise { - const logs = await this.logService.findLogs({ + const logs = await this.logService.findMany({ address, fromBlock, toBlock, diff --git a/packages/api/src/api/types.ts b/packages/api/src/api/types.ts index 27a57afe97..59eb2c7fa8 100644 --- a/packages/api/src/api/types.ts +++ b/packages/api/src/api/types.ts @@ -20,6 +20,7 @@ export enum ApiAccountAction { TokenBalance = "tokenbalance", TokenTransfers = "tokentx", NFTTransfers = "tokennfttx", + GetMinedBlocks = "getminedblocks", } export enum ApiContractAction { diff --git a/packages/api/src/batch/batch.controller.ts b/packages/api/src/batch/batch.controller.ts index 1d4bbf670b..5d0846a3b7 100644 --- a/packages/api/src/batch/batch.controller.ts +++ b/packages/api/src/batch/batch.controller.ts @@ -11,7 +11,7 @@ import { BatchDetailsDto } from "./batchDetails.dto"; const entityName = "batches"; -@ApiTags(entityName) +@ApiTags("Batch BFF") @Controller(entityName) export class BatchController { constructor(private readonly batchService: BatchService) {} diff --git a/packages/api/src/block/block.controller.ts b/packages/api/src/block/block.controller.ts index 941d48498b..a9cfaa7f16 100644 --- a/packages/api/src/block/block.controller.ts +++ b/packages/api/src/block/block.controller.ts @@ -11,7 +11,7 @@ import { BlockDetailDto } from "./blockDetail.dto"; const entityName = "blocks"; -@ApiTags(entityName) +@ApiTags("Block BFF") @Controller(entityName) export class BlockController { constructor(private readonly blockService: BlockService) {} diff --git a/packages/api/src/block/block.service.spec.ts b/packages/api/src/block/block.service.spec.ts index cdbcf88004..90884e8de7 100644 --- a/packages/api/src/block/block.service.spec.ts +++ b/packages/api/src/block/block.service.spec.ts @@ -4,7 +4,7 @@ import { getRepositoryToken } from "@nestjs/typeorm"; import { Repository, SelectQueryBuilder, FindOptionsOrder } from "typeorm"; import { Pagination, IPaginationMeta } from "nestjs-typeorm-paginate"; import * as utils from "../common/utils"; -import { BlockService } from "./block.service"; +import { BlockService, FindManyOptions } from "./block.service"; import { Block } from "./block.entity"; import { BlockDetail } from "./blockDetail.entity"; @@ -299,4 +299,81 @@ describe("BlockService", () => { expect(number).toBe(1000); }); }); + + describe("findMany", () => { + let queryBuilderMock; + let filterOptions: FindManyOptions; + + beforeEach(() => { + queryBuilderMock = mock>({ + getMany: jest.fn().mockResolvedValue([ + { + number: 1, + timestamp: new Date("2023-03-03"), + }, + ]), + }); + (blockDetailRepositoryMock.createQueryBuilder as jest.Mock).mockReturnValue(queryBuilderMock); + + filterOptions = { + miner: "address", + }; + }); + + it("creates query builder with proper params", async () => { + await service.findMany(filterOptions); + expect(blockDetailRepositoryMock.createQueryBuilder).toHaveBeenCalledTimes(1); + expect(blockDetailRepositoryMock.createQueryBuilder).toHaveBeenCalledWith("block"); + }); + + it("selects specified fields", async () => { + await service.findMany({ + ...filterOptions, + selectFields: ["number", "timestamp"], + }); + expect(queryBuilderMock.addSelect).toHaveBeenCalledTimes(1); + expect(queryBuilderMock.addSelect).toHaveBeenCalledWith(["number", "timestamp"]); + }); + + it("adds where condition for miner when specified", async () => { + await service.findMany(filterOptions); + expect(queryBuilderMock.where).toHaveBeenCalledTimes(1); + expect(queryBuilderMock.where).toHaveBeenCalledWith({ + miner: "address", + }); + }); + + it("does not add where condition for miner when not specified", async () => { + await service.findMany({}); + expect(queryBuilderMock.where).not.toBeCalled(); + }); + + it("sets offset and limit", async () => { + await service.findMany({ + page: 10, + offset: 100, + }); + expect(queryBuilderMock.offset).toHaveBeenCalledTimes(1); + expect(queryBuilderMock.offset).toHaveBeenCalledWith(900); + expect(queryBuilderMock.limit).toHaveBeenCalledTimes(1); + expect(queryBuilderMock.limit).toHaveBeenCalledWith(100); + }); + + it("orders by block number DESC", async () => { + await service.findMany({}); + expect(queryBuilderMock.orderBy).toHaveBeenCalledTimes(1); + expect(queryBuilderMock.orderBy).toHaveBeenCalledWith("block.number", "DESC"); + }); + + it("returns block list", async () => { + const result = await service.findMany({}); + expect(queryBuilderMock.getMany).toHaveBeenCalledTimes(1); + expect(result).toEqual([ + { + number: 1, + timestamp: new Date("2023-03-03"), + }, + ]); + }); + }); }); diff --git a/packages/api/src/block/block.service.ts b/packages/api/src/block/block.service.ts index c098ab766d..bc289d9886 100644 --- a/packages/api/src/block/block.service.ts +++ b/packages/api/src/block/block.service.ts @@ -7,6 +7,13 @@ import { IPaginationOptions } from "../common/types"; import { Block } from "./block.entity"; import { BlockDetail } from "./blockDetail.entity"; +export interface FindManyOptions { + miner?: string; + page?: number; + offset?: number; + selectFields?: (keyof BlockDetail)[]; +} + @Injectable() export class BlockService { public constructor( @@ -82,4 +89,18 @@ export class BlockService { return await paginate(queryBuilder, paginationOptions, () => this.count(filterOptions)); } + + public async findMany({ miner, page = 1, offset = 10, selectFields }: FindManyOptions): Promise { + const queryBuilder = this.blockDetailsRepository.createQueryBuilder("block"); + queryBuilder.addSelect(selectFields); + if (miner) { + queryBuilder.where({ + miner, + }); + } + queryBuilder.offset((page - 1) * offset); + queryBuilder.limit(offset); + queryBuilder.orderBy("block.number", "DESC"); + return await queryBuilder.getMany(); + } } diff --git a/packages/api/src/log/log.service.spec.ts b/packages/api/src/log/log.service.spec.ts index 96c7017b5b..ea68bc16bf 100644 --- a/packages/api/src/log/log.service.spec.ts +++ b/packages/api/src/log/log.service.spec.ts @@ -101,7 +101,7 @@ describe("LogService", () => { }); }); - describe("findLogs", () => { + describe("findMany", () => { let queryBuilderMock; let filterOptions: FilterLogsByAddressOptions; @@ -127,26 +127,26 @@ describe("LogService", () => { }); it("creates query builder with proper params", async () => { - await service.findLogs(filterOptions); + await service.findMany(filterOptions); expect(repositoryMock.createQueryBuilder).toHaveBeenCalledTimes(1); expect(repositoryMock.createQueryBuilder).toHaveBeenCalledWith("log"); }); it("joins transaction and transactionReceipt records to the logs", async () => { - await service.findLogs(filterOptions); + await service.findMany(filterOptions); expect(queryBuilderMock.leftJoin).toBeCalledTimes(2); expect(queryBuilderMock.leftJoin).toHaveBeenCalledWith("log.transaction", "transaction"); expect(queryBuilderMock.leftJoin).toHaveBeenCalledWith("transaction.transactionReceipt", "transactionReceipt"); }); it("selects only needed fields from joined records", async () => { - await service.findLogs(filterOptions); + await service.findMany(filterOptions); expect(queryBuilderMock.addSelect).toBeCalledTimes(1); expect(queryBuilderMock.addSelect).toHaveBeenCalledWith(["transaction.gasPrice", "transactionReceipt.gasUsed"]); }); it("filters logs by address", async () => { - await service.findLogs(filterOptions); + await service.findMany(filterOptions); expect(queryBuilderMock.where).toBeCalledTimes(1); expect(queryBuilderMock.where).toHaveBeenCalledWith({ address: filterOptions.address, @@ -155,7 +155,7 @@ describe("LogService", () => { describe("when fromBlock filter is specified", () => { it("adds blockNumber filter", async () => { - await service.findLogs({ + await service.findMany({ ...filterOptions, fromBlock: 10, }); @@ -168,7 +168,7 @@ describe("LogService", () => { describe("when toBlock filter is specified", () => { it("adds toBlock filter", async () => { - await service.findLogs({ + await service.findMany({ ...filterOptions, toBlock: 10, }); @@ -180,7 +180,7 @@ describe("LogService", () => { }); it("sets offset and limit", async () => { - await service.findLogs({ + await service.findMany({ ...filterOptions, page: 2, offset: 100, @@ -192,7 +192,7 @@ describe("LogService", () => { }); it("sorts by blockNumber asc and logIndex asc", async () => { - await service.findLogs(filterOptions); + await service.findMany(filterOptions); expect(queryBuilderMock.orderBy).toBeCalledTimes(1); expect(queryBuilderMock.orderBy).toHaveBeenCalledWith("log.blockNumber", "ASC"); expect(queryBuilderMock.addOrderBy).toBeCalledTimes(1); @@ -200,7 +200,7 @@ describe("LogService", () => { }); it("executes query and returns transfers list", async () => { - const result = await service.findLogs(filterOptions); + const result = await service.findMany(filterOptions); expect(result).toEqual([ { logIndex: 1, diff --git a/packages/api/src/log/log.service.ts b/packages/api/src/log/log.service.ts index 6f65dabba1..c06f936b85 100644 --- a/packages/api/src/log/log.service.ts +++ b/packages/api/src/log/log.service.ts @@ -37,7 +37,7 @@ export class LogService { return await paginate(queryBuilder, paginationOptions); } - public async findLogs({ + public async findMany({ address, fromBlock, toBlock, diff --git a/packages/api/src/stats/stats.controller.ts b/packages/api/src/stats/stats.controller.ts index eff5b73b64..ae8b9e105c 100644 --- a/packages/api/src/stats/stats.controller.ts +++ b/packages/api/src/stats/stats.controller.ts @@ -8,7 +8,7 @@ import { StatsDto } from "./stats.dto"; const entityName = "stats"; -@ApiTags(entityName) +@ApiTags("Stats BFF") @Controller(entityName) export class StatsController { constructor( diff --git a/packages/api/src/token/token.controller.ts b/packages/api/src/token/token.controller.ts index fb0991e856..096fd54099 100644 --- a/packages/api/src/token/token.controller.ts +++ b/packages/api/src/token/token.controller.ts @@ -11,7 +11,7 @@ import { ParseAddressPipe, ADDRESS_REGEX_PATTERN } from "../common/pipes/parseAd const entityName = "tokens"; -@ApiTags(entityName) +@ApiTags("Token BFF") @Controller(entityName) export class TokenController { constructor(private readonly tokenService: TokenService, private readonly transferService: TransferService) {} diff --git a/packages/api/src/transaction/transaction.controller.ts b/packages/api/src/transaction/transaction.controller.ts index a8e5bc95f9..ffd0a4dd66 100644 --- a/packages/api/src/transaction/transaction.controller.ts +++ b/packages/api/src/transaction/transaction.controller.ts @@ -15,7 +15,7 @@ import { ParseTransactionHashPipe, TX_HASH_REGEX_PATTERN } from "../common/pipes const entityName = "transactions"; -@ApiTags(entityName) +@ApiTags("Transaction BFF") @Controller(entityName) export class TransactionController { constructor( diff --git a/packages/api/test/account-api.e2e-spec.ts b/packages/api/test/account-api.e2e-spec.ts index 84417ee430..a395a00023 100644 --- a/packages/api/test/account-api.e2e-spec.ts +++ b/packages/api/test/account-api.e2e-spec.ts @@ -303,4 +303,25 @@ describe("Account API (e2e)", () => { ); }); }); + + describe("/api?module=account&action=getminedblocks GET", () => { + it("returns HTTP 200 and list of mined blocks by address", () => { + return request(app.getHttpServer()) + .get(`/api?module=account&action=getminedblocks&address=0x0000000000000000000000000000000000000000`) + .expect(200) + .expect((res) => + expect(res.body).toStrictEqual({ + status: "1", + message: "OK", + result: [ + { + blockNumber: "1", + timeStamp: "1668091448", + blockReward: "0", + }, + ], + }) + ); + }); + }); }); diff --git a/packages/worker/src/entities/block.entity.ts b/packages/worker/src/entities/block.entity.ts index 3faa677179..59cdc85c59 100644 --- a/packages/worker/src/entities/block.entity.ts +++ b/packages/worker/src/entities/block.entity.ts @@ -9,6 +9,7 @@ import { BaseEntity } from "./base.entity"; @Entity({ name: "blocks" }) @Index(["timestamp", "number"]) +@Index(["miner", "number"]) export class Block extends BaseEntity { @PrimaryColumn({ type: "bigint", transformer: bigIntNumberTransformer }) public readonly number: number; diff --git a/packages/worker/src/migrations/1696897107720-AddBlockMinerIndex.ts b/packages/worker/src/migrations/1696897107720-AddBlockMinerIndex.ts new file mode 100644 index 0000000000..d40ed2a326 --- /dev/null +++ b/packages/worker/src/migrations/1696897107720-AddBlockMinerIndex.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddBlockMinerIndex1696897107720 implements MigrationInterface { + name = "AddBlockMinerIndex1696897107720"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE INDEX "IDX_1ccfbf9a34c2be286db278de8c" ON "blocks" ("miner", "number") `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_1ccfbf9a34c2be286db278de8c"`); + } +}