From bb89707c5336753fe820525b25a603ab70d5162b Mon Sep 17 00:00:00 2001 From: Vasyl Ivanchuk Date: Tue, 5 Dec 2023 14:29:41 +0200 Subject: [PATCH 1/4] feat: return gasUsed, gasPerPubdata, maxFeePerGas and maxPriorityFeePerGas fields for transaction (#109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ Return `gasUsed`, `gasPerPubdata`, `maxFeePerGas` and `maxPriorityFeePerGas` fields for transaction. ## Why ❔ For better UX we want to show gas and fee related fields on UI so we need to return these fields from the API first. ## Checklist - [X] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [X] Tests for the changes have been added / updated. --- .../api/account/account.controller.spec.ts | 4 +- .../transaction.controller.spec.ts | 7 +- packages/api/src/block/block.controller.ts | 6 +- packages/api/src/block/block.module.ts | 4 +- packages/api/src/block/block.service.spec.ts | 10 +- packages/api/src/block/block.service.ts | 16 +- ...blockDetail.dto.ts => blockDetails.dto.ts} | 2 +- ...etail.entity.ts => blockDetails.entity.ts} | 2 +- .../hexToDecimalNumber.transformer.spec.ts | 27 +++ .../hexToDecimalNumber.transformer.ts | 17 ++ .../src/transaction/dtos/transaction.dto.ts | 43 +++- .../dtos/transactionDetails.dto.ts | 11 + .../entities/transaction.entity.ts | 10 + .../entities/transactionDetails.entity.ts | 18 ++ .../src/transaction/transaction.controller.ts | 9 +- .../api/src/transaction/transaction.module.ts | 3 +- .../transaction/transaction.service.spec.ts | 51 ++++- .../src/transaction/transaction.service.ts | 12 +- packages/api/test/account-api.e2e-spec.ts | 6 +- packages/api/test/address.e2e-spec.ts | 6 +- packages/api/test/block-api.e2e-spec.ts | 6 +- packages/api/test/block.e2e-spec.ts | 6 +- packages/api/test/log-api.e2e-spec.ts | 6 +- packages/api/test/stats-api.e2e-spec.ts | 6 +- packages/api/test/stats.e2e-spec.ts | 6 +- packages/api/test/token-api.e2e-spec.ts | 6 +- packages/api/test/token.e2e-spec.ts | 6 +- packages/api/test/transaction-api.e2e-spec.ts | 6 +- packages/api/test/transaction.e2e-spec.ts | 191 +++++++++++++----- 29 files changed, 381 insertions(+), 122 deletions(-) rename packages/api/src/block/{blockDetail.dto.ts => blockDetails.dto.ts} (98%) rename packages/api/src/block/{blockDetail.entity.ts => blockDetails.entity.ts} (97%) create mode 100644 packages/api/src/common/transformers/hexToDecimalNumber.transformer.spec.ts create mode 100644 packages/api/src/common/transformers/hexToDecimalNumber.transformer.ts create mode 100644 packages/api/src/transaction/dtos/transactionDetails.dto.ts create mode 100644 packages/api/src/transaction/entities/transactionDetails.entity.ts diff --git a/packages/api/src/api/account/account.controller.spec.ts b/packages/api/src/api/account/account.controller.spec.ts index 912a81c166..f5b2f14c6e 100644 --- a/packages/api/src/api/account/account.controller.spec.ts +++ b/packages/api/src/api/account/account.controller.spec.ts @@ -3,7 +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 { BlockDetails } from "../../block/blockDetails.entity"; import { TransactionService } from "../../transaction/transaction.service"; import { BalanceService } from "../../balance/balance.service"; import { TransactionStatus } from "../../transaction/entities/transaction.entity"; @@ -629,7 +629,7 @@ describe("AccountController", () => { 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]); + .mockResolvedValue([{ number: 1, timestamp: new Date("2023-03-03") } as BlockDetails]); const response = await controller.getAccountMinedBlocks(address, { page: 1, offset: 10, diff --git a/packages/api/src/api/transaction/transaction.controller.spec.ts b/packages/api/src/api/transaction/transaction.controller.spec.ts index 5e85dddc84..a74a252aa0 100644 --- a/packages/api/src/api/transaction/transaction.controller.spec.ts +++ b/packages/api/src/api/transaction/transaction.controller.spec.ts @@ -3,7 +3,8 @@ import { mock } from "jest-mock-extended"; import { Logger } from "@nestjs/common"; import { TransactionService } from "../../transaction/transaction.service"; import { TransactionReceiptService } from "../../transaction/transactionReceipt.service"; -import { TransactionStatus, Transaction } from "../../transaction/entities/transaction.entity"; +import { TransactionStatus } from "../../transaction/entities/transaction.entity"; +import { TransactionDetails } from "../../transaction/entities/transactionDetails.entity"; import { TransactionReceipt } from "../../transaction/entities/transactionReceipt.entity"; import { ResponseStatus, ResponseMessage } from "../dtos/common/responseBase.dto"; import { TransactionController } from "./transaction.controller"; @@ -56,7 +57,7 @@ describe("TransactionController", () => { it("returns isError as 0 when transaction is successful", async () => { jest .spyOn(transactionServiceMock, "findOne") - .mockResolvedValue({ status: TransactionStatus.Included } as Transaction); + .mockResolvedValue({ status: TransactionStatus.Included } as TransactionDetails); const response = await controller.getTransactionStatus(transactionHash); expect(response).toEqual({ @@ -72,7 +73,7 @@ describe("TransactionController", () => { it("returns isError as 1 when transaction is failed", async () => { jest .spyOn(transactionServiceMock, "findOne") - .mockResolvedValue({ status: TransactionStatus.Failed } as Transaction); + .mockResolvedValue({ status: TransactionStatus.Failed } as TransactionDetails); const response = await controller.getTransactionStatus(transactionHash); expect(response).toEqual({ diff --git a/packages/api/src/block/block.controller.ts b/packages/api/src/block/block.controller.ts index 25488e4506..f9a3a4febf 100644 --- a/packages/api/src/block/block.controller.ts +++ b/packages/api/src/block/block.controller.ts @@ -14,7 +14,7 @@ import { PagingOptionsDto, ListFiltersDto } from "../common/dtos"; import { ApiListPageOkResponse } from "../common/decorators/apiListPageOkResponse"; import { BlockService } from "./block.service"; import { BlockDto } from "./block.dto"; -import { BlockDetailDto } from "./blockDetail.dto"; +import { BlockDetailsDto } from "./blockDetails.dto"; import { swagger } from "../config/featureFlags"; const entityName = "blocks"; @@ -48,12 +48,12 @@ export class BlockController { example: "1", description: "Block number", }) - @ApiOkResponse({ description: "Block was returned successfully", type: BlockDetailDto }) + @ApiOkResponse({ description: "Block was returned successfully", type: BlockDetailsDto }) @ApiBadRequestResponse({ description: "Block number is invalid" }) @ApiNotFoundResponse({ description: "Block with the specified number does not exist" }) public async getBlock( @Param("blockNumber", new ParseLimitedIntPipe({ min: 0 })) blockNumber: number - ): Promise { + ): Promise { const block = await this.blockService.findOne(blockNumber); if (!block) { throw new NotFoundException(); diff --git a/packages/api/src/block/block.module.ts b/packages/api/src/block/block.module.ts index cbca1ea4d3..05687a8292 100644 --- a/packages/api/src/block/block.module.ts +++ b/packages/api/src/block/block.module.ts @@ -3,10 +3,10 @@ import { TypeOrmModule } from "@nestjs/typeorm"; import { BlockService } from "../block/block.service"; import { BlockController } from "./block.controller"; import { Block } from "./block.entity"; -import { BlockDetail } from "./blockDetail.entity"; +import { BlockDetails } from "./blockDetails.entity"; @Module({ - imports: [TypeOrmModule.forFeature([Block, BlockDetail])], + imports: [TypeOrmModule.forFeature([Block, BlockDetails])], controllers: [BlockController], providers: [BlockService], exports: [BlockService], diff --git a/packages/api/src/block/block.service.spec.ts b/packages/api/src/block/block.service.spec.ts index 90884e8de7..b1d2c853da 100644 --- a/packages/api/src/block/block.service.spec.ts +++ b/packages/api/src/block/block.service.spec.ts @@ -6,7 +6,7 @@ import { Pagination, IPaginationMeta } from "nestjs-typeorm-paginate"; import * as utils from "../common/utils"; import { BlockService, FindManyOptions } from "./block.service"; import { Block } from "./block.entity"; -import { BlockDetail } from "./blockDetail.entity"; +import { BlockDetails } from "./blockDetails.entity"; jest.mock("../common/utils"); @@ -14,11 +14,11 @@ describe("BlockService", () => { let blockRecord; let service: BlockService; let repositoryMock: Repository; - let blockDetailRepositoryMock: Repository; + let blockDetailRepositoryMock: Repository; beforeEach(async () => { repositoryMock = mock>(); - blockDetailRepositoryMock = mock>(); + blockDetailRepositoryMock = mock>(); blockRecord = { number: 123, @@ -32,7 +32,7 @@ describe("BlockService", () => { useValue: repositoryMock, }, { - provide: getRepositoryToken(BlockDetail), + provide: getRepositoryToken(BlockDetails), useValue: blockDetailRepositoryMock, }, ], @@ -305,7 +305,7 @@ describe("BlockService", () => { let filterOptions: FindManyOptions; beforeEach(() => { - queryBuilderMock = mock>({ + queryBuilderMock = mock>({ getMany: jest.fn().mockResolvedValue([ { number: 1, diff --git a/packages/api/src/block/block.service.ts b/packages/api/src/block/block.service.ts index bc289d9886..ef5f37735c 100644 --- a/packages/api/src/block/block.service.ts +++ b/packages/api/src/block/block.service.ts @@ -5,13 +5,13 @@ import { Pagination } from "nestjs-typeorm-paginate"; import { paginate } from "../common/utils"; import { IPaginationOptions } from "../common/types"; import { Block } from "./block.entity"; -import { BlockDetail } from "./blockDetail.entity"; +import { BlockDetails } from "./blockDetails.entity"; export interface FindManyOptions { miner?: string; page?: number; offset?: number; - selectFields?: (keyof BlockDetail)[]; + selectFields?: (keyof BlockDetails)[]; } @Injectable() @@ -19,8 +19,8 @@ export class BlockService { public constructor( @InjectRepository(Block) private readonly blocksRepository: Repository, - @InjectRepository(BlockDetail) - private readonly blockDetailsRepository: Repository + @InjectRepository(BlockDetails) + private readonly blockDetailsRepository: Repository ) {} private getBlock(filterOptions: FindOptionsWhere, orderOptions: FindOptionsOrder): Promise { @@ -50,9 +50,9 @@ export class BlockService { public async findOne( number: number, - selectFields?: (keyof BlockDetail)[], - relations: FindOptionsRelations = { batch: true } - ): Promise { + selectFields?: (keyof BlockDetails)[], + relations: FindOptionsRelations = { batch: true } + ): Promise { return await this.blockDetailsRepository.findOne({ where: { number }, relations: relations, @@ -90,7 +90,7 @@ export class BlockService { return await paginate(queryBuilder, paginationOptions, () => this.count(filterOptions)); } - public async findMany({ miner, page = 1, offset = 10, selectFields }: FindManyOptions): Promise { + public async findMany({ miner, page = 1, offset = 10, selectFields }: FindManyOptions): Promise { const queryBuilder = this.blockDetailsRepository.createQueryBuilder("block"); queryBuilder.addSelect(selectFields); if (miner) { diff --git a/packages/api/src/block/blockDetail.dto.ts b/packages/api/src/block/blockDetails.dto.ts similarity index 98% rename from packages/api/src/block/blockDetail.dto.ts rename to packages/api/src/block/blockDetails.dto.ts index 6034d3c86b..d80673960e 100644 --- a/packages/api/src/block/blockDetail.dto.ts +++ b/packages/api/src/block/blockDetails.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from "@nestjs/swagger"; import { BlockDto } from "./block.dto"; -export class BlockDetailDto extends BlockDto { +export class BlockDetailsDto extends BlockDto { @ApiProperty({ type: String, description: "The hash of the previous block", diff --git a/packages/api/src/block/blockDetail.entity.ts b/packages/api/src/block/blockDetails.entity.ts similarity index 97% rename from packages/api/src/block/blockDetail.entity.ts rename to packages/api/src/block/blockDetails.entity.ts index b5d81236eb..151f812a98 100644 --- a/packages/api/src/block/blockDetail.entity.ts +++ b/packages/api/src/block/blockDetails.entity.ts @@ -3,7 +3,7 @@ import { Block } from "./block.entity"; import { hexTransformer } from "../common/transformers/hex.transformer"; @Entity({ name: "blocks" }) -export class BlockDetail extends Block { +export class BlockDetails extends Block { @Column({ type: "bytea", transformer: hexTransformer, nullable: true }) public readonly parentHash?: string; diff --git a/packages/api/src/common/transformers/hexToDecimalNumber.transformer.spec.ts b/packages/api/src/common/transformers/hexToDecimalNumber.transformer.spec.ts new file mode 100644 index 0000000000..b53f7c9d9a --- /dev/null +++ b/packages/api/src/common/transformers/hexToDecimalNumber.transformer.spec.ts @@ -0,0 +1,27 @@ +import { hexToDecimalNumberTransformer } from "./hexToDecimalNumber.transformer"; + +describe("hexToDecimalNumberTransformer", () => { + describe("to", () => { + it("returns null for null input", () => { + const result = hexToDecimalNumberTransformer.to(null); + expect(result).toBeNull(); + }); + + it("returns hex representation of the decimal number string", () => { + const result = hexToDecimalNumberTransformer.to("800"); + expect(result).toBe("0x0320"); + }); + }); + + describe("from", () => { + it("returns null for null input", () => { + const result = hexToDecimalNumberTransformer.from(null); + expect(result).toBeNull(); + }); + + it("returns decimal representation of the hex number string", () => { + const result = hexToDecimalNumberTransformer.from("0x320"); + expect(result).toBe("800"); + }); + }); +}); diff --git a/packages/api/src/common/transformers/hexToDecimalNumber.transformer.ts b/packages/api/src/common/transformers/hexToDecimalNumber.transformer.ts new file mode 100644 index 0000000000..edb849a2a8 --- /dev/null +++ b/packages/api/src/common/transformers/hexToDecimalNumber.transformer.ts @@ -0,0 +1,17 @@ +import { BigNumber } from "ethers"; +import { ValueTransformer } from "typeorm"; + +export const hexToDecimalNumberTransformer: ValueTransformer = { + to(decimalNumberStr: string | null): string | null { + if (!decimalNumberStr) { + return null; + } + return BigNumber.from(decimalNumberStr).toHexString(); + }, + from(hexNumberStr: string | null): string | null { + if (!hexNumberStr) { + return null; + } + return BigNumber.from(hexNumberStr).toString(); + }, +}; diff --git a/packages/api/src/transaction/dtos/transaction.dto.ts b/packages/api/src/transaction/dtos/transaction.dto.ts index 94ee8676ad..8b77f54a1d 100644 --- a/packages/api/src/transaction/dtos/transaction.dto.ts +++ b/packages/api/src/transaction/dtos/transaction.dto.ts @@ -40,7 +40,7 @@ export class TransactionDto { @ApiProperty({ type: String, description: "The amount this transaction sent", - example: "0x2386f26fc10000", + example: "100000000", }) public readonly value: string; @@ -58,6 +58,47 @@ export class TransactionDto { }) public readonly nonce: number; + @ApiProperty({ + type: String, + description: "Gas price", + example: "100000000", + }) + public readonly gasPrice: string; + + @ApiProperty({ + type: String, + description: "Gas limit", + example: "100000000", + }) + public readonly gasLimit: string; + + @ApiProperty({ + type: String, + description: "Gas per pubdata limit", + example: "100000000", + examples: ["100000000", null], + required: false, + }) + public readonly gasPerPubdata?: string; + + @ApiProperty({ + type: String, + description: "Max fee per gas", + example: "100000000", + examples: ["100000000", null], + required: false, + }) + public readonly maxFeePerGas?: string; + + @ApiProperty({ + type: String, + description: "Max priority fee per gas", + example: "100000000", + examples: ["100000000", null], + required: false, + }) + public readonly maxPriorityFeePerGas?: string; + @ApiProperty({ type: Number, description: "The number (height) of the block this transaction was mined in", diff --git a/packages/api/src/transaction/dtos/transactionDetails.dto.ts b/packages/api/src/transaction/dtos/transactionDetails.dto.ts new file mode 100644 index 0000000000..0f18c8b72a --- /dev/null +++ b/packages/api/src/transaction/dtos/transactionDetails.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { TransactionDto } from "./transaction.dto"; + +export class TransactionDetailsDto extends TransactionDto { + @ApiProperty({ + type: String, + description: "Gas used by the transaction", + example: "50000000", + }) + public readonly gasUsed: string; +} diff --git a/packages/api/src/transaction/entities/transaction.entity.ts b/packages/api/src/transaction/entities/transaction.entity.ts index 60a0ac0566..e4a1b357e8 100644 --- a/packages/api/src/transaction/entities/transaction.entity.ts +++ b/packages/api/src/transaction/entities/transaction.entity.ts @@ -3,6 +3,7 @@ import { BaseEntity } from "../../common/entities/base.entity"; import { normalizeAddressTransformer } from "../../common/transformers/normalizeAddress.transformer"; import { bigIntNumberTransformer } from "../../common/transformers/bigIntNumber.transformer"; import { hexTransformer } from "../../common/transformers/hex.transformer"; +import { hexToDecimalNumberTransformer } from "../../common/transformers/hexToDecimalNumber.transformer"; import { TransactionReceipt } from "./transactionReceipt.entity"; import { Transfer } from "../../transfer/transfer.entity"; import { Block } from "../../block/block.entity"; @@ -61,6 +62,15 @@ export class Transaction extends BaseEntity { @Column({ type: "varchar", length: 128 }) public readonly gasPrice: string; + @Column({ type: "varchar", length: 128, nullable: true, transformer: hexToDecimalNumberTransformer }) + public readonly gasPerPubdata?: string; + + @Column({ type: "varchar", length: 128, nullable: true }) + public readonly maxFeePerGas?: string; + + @Column({ type: "varchar", length: 128, nullable: true }) + public readonly maxPriorityFeePerGas?: string; + @ManyToOne(() => Block) @JoinColumn({ name: "blockNumber" }) public readonly block: Block; diff --git a/packages/api/src/transaction/entities/transactionDetails.entity.ts b/packages/api/src/transaction/entities/transactionDetails.entity.ts new file mode 100644 index 0000000000..1b8fdb9f4f --- /dev/null +++ b/packages/api/src/transaction/entities/transactionDetails.entity.ts @@ -0,0 +1,18 @@ +import { Entity } from "typeorm"; +import { Transaction } from "./transaction.entity"; + +@Entity({ name: "transactions" }) +export class TransactionDetails extends Transaction { + public get gasUsed(): string { + return this.transactionReceipt ? this.transactionReceipt.gasUsed : null; + } + + toJSON(): any { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { transactionReceipt, ...restFields } = super.toJSON(); + return { + ...restFields, + gasUsed: this.gasUsed, + }; + } +} diff --git a/packages/api/src/transaction/transaction.controller.ts b/packages/api/src/transaction/transaction.controller.ts index 087e73eecd..f8465723d9 100644 --- a/packages/api/src/transaction/transaction.controller.ts +++ b/packages/api/src/transaction/transaction.controller.ts @@ -14,6 +14,7 @@ import { buildDateFilter } from "../common/utils"; import { FilterTransactionsOptionsDto } from "./dtos/filterTransactionsOptions.dto"; import { TransferDto } from "../transfer/transfer.dto"; import { TransactionDto } from "./dtos/transaction.dto"; +import { TransactionDetailsDto } from "./dtos/transactionDetails.dto"; import { TransferService } from "../transfer/transfer.service"; import { LogDto } from "../log/log.dto"; import { LogService } from "../log/log.service"; @@ -72,12 +73,12 @@ export class TransactionController { @ApiNotFoundResponse({ description: "Transaction with the specified hash does not exist" }) public async getTransaction( @Param("transactionHash", new ParseTransactionHashPipe()) transactionHash: string - ): Promise { - const transaction = await this.transactionService.findOne(transactionHash); - if (!transaction) { + ): Promise { + const transactionDetail = await this.transactionService.findOne(transactionHash); + if (!transactionDetail) { throw new NotFoundException(); } - return transaction; + return transactionDetail; } @Get(":transactionHash/transfers") diff --git a/packages/api/src/transaction/transaction.module.ts b/packages/api/src/transaction/transaction.module.ts index 299313a968..b121a319bd 100644 --- a/packages/api/src/transaction/transaction.module.ts +++ b/packages/api/src/transaction/transaction.module.ts @@ -4,6 +4,7 @@ import { TransactionController } from "./transaction.controller"; import { TransactionService } from "./transaction.service"; import { TransactionReceiptService } from "./transactionReceipt.service"; import { Transaction } from "./entities/transaction.entity"; +import { TransactionDetails } from "./entities/transactionDetails.entity"; import { AddressTransaction } from "./entities/addressTransaction.entity"; import { TransactionReceipt } from "./entities/transactionReceipt.entity"; import { Batch } from "../batch/batch.entity"; @@ -13,7 +14,7 @@ import { LogModule } from "../log/log.module"; @Module({ imports: [ - TypeOrmModule.forFeature([Transaction, AddressTransaction, TransactionReceipt, Batch]), + TypeOrmModule.forFeature([Transaction, TransactionDetails, AddressTransaction, TransactionReceipt, Batch]), TransferModule, LogModule, CounterModule, diff --git a/packages/api/src/transaction/transaction.service.spec.ts b/packages/api/src/transaction/transaction.service.spec.ts index 788582b82f..b8c5e7e3cb 100644 --- a/packages/api/src/transaction/transaction.service.spec.ts +++ b/packages/api/src/transaction/transaction.service.spec.ts @@ -8,6 +8,7 @@ import { SortingOrder } from "../common/types"; import { CounterService } from "../counter/counter.service"; import { TransactionService, FilterTransactionsOptions } from "./transaction.service"; import { Transaction } from "./entities/transaction.entity"; +import { TransactionDetails } from "./entities/transactionDetails.entity"; import { AddressTransaction } from "./entities/addressTransaction.entity"; import { Batch } from "../batch/batch.entity"; @@ -17,6 +18,7 @@ describe("TransactionService", () => { let transaction; let service: TransactionService; let repositoryMock: typeorm.Repository; + let repositoryDetailMock: typeorm.Repository; let addressTransactionRepositoryMock: typeorm.Repository; let batchRepositoryMock: typeorm.Repository; let counterServiceMock: CounterService; @@ -25,6 +27,7 @@ describe("TransactionService", () => { beforeEach(async () => { counterServiceMock = mock(); repositoryMock = mock>(); + repositoryDetailMock = mock>(); addressTransactionRepositoryMock = mock>(); batchRepositoryMock = mock>(); transaction = { @@ -38,6 +41,10 @@ describe("TransactionService", () => { provide: getRepositoryToken(Transaction), useValue: repositoryMock, }, + { + provide: getRepositoryToken(TransactionDetails), + useValue: repositoryDetailMock, + }, { provide: getRepositoryToken(AddressTransaction), useValue: addressTransactionRepositoryMock, @@ -61,21 +68,45 @@ describe("TransactionService", () => { }); describe("findOne", () => { + let queryBuilderMock; + const hash = "txHash"; + beforeEach(() => { - (repositoryMock.findOne as jest.Mock).mockResolvedValue(transaction); + queryBuilderMock = mock>(); + (repositoryDetailMock.createQueryBuilder as jest.Mock).mockReturnValue(queryBuilderMock); + (queryBuilderMock.getOne as jest.Mock).mockResolvedValue(null); }); - it("queries transactions by specified transaction hash", async () => { - await service.findOne(transactionHash); - expect(repositoryMock.findOne).toHaveBeenCalledTimes(1); - expect(repositoryMock.findOne).toHaveBeenCalledWith({ - where: { hash: transactionHash }, - relations: { batch: true }, - }); + it("creates query builder with proper params", async () => { + await service.findOne(hash); + expect(repositoryDetailMock.createQueryBuilder).toHaveBeenCalledWith("transaction"); }); - it("returns transaction by hash", async () => { - const result = await service.findOne(transactionHash); + it("filters transactions by the specified hash", async () => { + await service.findOne(hash); + expect(queryBuilderMock.where).toHaveBeenCalledWith({ hash }); + }); + + it("joins batch record to get batch specific fields", async () => { + await service.findOne(hash); + expect(queryBuilderMock.leftJoinAndSelect).toHaveBeenCalledWith("transaction.batch", "batch"); + }); + + it("joins transactionReceipt record to get transactionReceipt specific fields", async () => { + await service.findOne(hash); + expect(queryBuilderMock.leftJoin).toHaveBeenCalledWith("transaction.transactionReceipt", "transactionReceipt"); + }); + + it("selects only needed transactionReceipt fields", async () => { + await service.findOne(hash); + expect(queryBuilderMock.addSelect).toHaveBeenCalledWith(["transactionReceipt.gasUsed"]); + }); + + it("returns paginated result", async () => { + const transaction = mock(); + (queryBuilderMock.getOne as jest.Mock).mockResolvedValue(transaction); + + const result = await service.findOne(hash); expect(result).toBe(transaction); }); }); diff --git a/packages/api/src/transaction/transaction.service.ts b/packages/api/src/transaction/transaction.service.ts index 32661d830d..a79376148f 100644 --- a/packages/api/src/transaction/transaction.service.ts +++ b/packages/api/src/transaction/transaction.service.ts @@ -5,6 +5,7 @@ import { Pagination } from "nestjs-typeorm-paginate"; import { paginate } from "../common/utils"; import { IPaginationOptions, CounterCriteria, SortingOrder } from "../common/types"; import { Transaction } from "./entities/transaction.entity"; +import { TransactionDetails } from "./entities/transactionDetails.entity"; import { AddressTransaction } from "./entities/addressTransaction.entity"; import { Batch } from "../batch/batch.entity"; import { CounterService } from "../counter/counter.service"; @@ -29,6 +30,8 @@ export class TransactionService { constructor( @InjectRepository(Transaction) private readonly transactionRepository: Repository, + @InjectRepository(TransactionDetails) + private readonly transactionDetailsRepository: Repository, @InjectRepository(AddressTransaction) private readonly addressTransactionRepository: Repository, @InjectRepository(Batch) @@ -36,8 +39,13 @@ export class TransactionService { private readonly counterService: CounterService ) {} - public async findOne(hash: string): Promise { - return await this.transactionRepository.findOne({ where: { hash }, relations: { batch: true } }); + public async findOne(hash: string): Promise { + const queryBuilder = this.transactionDetailsRepository.createQueryBuilder("transaction"); + queryBuilder.leftJoinAndSelect("transaction.batch", "batch"); + queryBuilder.leftJoin("transaction.transactionReceipt", "transactionReceipt"); + queryBuilder.addSelect(["transactionReceipt.gasUsed"]); + queryBuilder.where({ hash }); + return await queryBuilder.getOne(); } public async exists(hash: string): Promise { diff --git a/packages/api/test/account-api.e2e-spec.ts b/packages/api/test/account-api.e2e-spec.ts index 340d95cd58..f529cc56aa 100644 --- a/packages/api/test/account-api.e2e-spec.ts +++ b/packages/api/test/account-api.e2e-spec.ts @@ -4,7 +4,7 @@ import { getRepositoryToken } from "@nestjs/typeorm"; import * as request from "supertest"; import { Repository } from "typeorm"; import { BatchDetails } from "../src/batch/batchDetails.entity"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { AddressTransaction } from "../src/transaction/entities/addressTransaction.entity"; import { Transaction } from "../src/transaction/entities/transaction.entity"; import { TransactionReceipt } from "../src/transaction/entities/transactionReceipt.entity"; @@ -23,7 +23,7 @@ describe("Account API (e2e)", () => { let addressTransferRepository: Repository; let transferRepository: Repository; let transactionReceiptRepository: Repository; - let blockRepository: Repository; + let blockRepository: Repository; let batchRepository: Repository; let tokenRepository: Repository; let balanceRepository: Repository; @@ -42,7 +42,7 @@ describe("Account API (e2e)", () => { addressTransferRepository = app.get>(getRepositoryToken(AddressTransfer)); transferRepository = app.get>(getRepositoryToken(Transfer)); transactionReceiptRepository = app.get>(getRepositoryToken(TransactionReceipt)); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); batchRepository = app.get>(getRepositoryToken(BatchDetails)); tokenRepository = app.get>(getRepositoryToken(Token)); balanceRepository = app.get>(getRepositoryToken(Balance)); diff --git a/packages/api/test/address.e2e-spec.ts b/packages/api/test/address.e2e-spec.ts index 68fc5ff46c..772878a9ee 100644 --- a/packages/api/test/address.e2e-spec.ts +++ b/packages/api/test/address.e2e-spec.ts @@ -7,7 +7,7 @@ import { AppModule } from "../src/app.module"; import { configureApp } from "../src/configureApp"; import { Address } from "../src/address/address.entity"; import { Balance } from "../src/balance/balance.entity"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { Transaction } from "../src/transaction/entities/transaction.entity"; import { AddressTransaction } from "../src/transaction/entities/addressTransaction.entity"; import { TransactionReceipt } from "../src/transaction/entities/transactionReceipt.entity"; @@ -21,7 +21,7 @@ import { AddressTransfer } from "../src/transfer/addressTransfer.entity"; describe("AddressController (e2e)", () => { let app: INestApplication; let addressRepository: Repository
; - let blockRepository: Repository; + let blockRepository: Repository; let transactionRepository: Repository; let addressTransactionRepository: Repository; let transactionReceiptRepository: Repository; @@ -45,7 +45,7 @@ describe("AddressController (e2e)", () => { await app.init(); addressRepository = app.get>(getRepositoryToken(Address)); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); transactionRepository = app.get>(getRepositoryToken(Transaction)); addressTransactionRepository = app.get>(getRepositoryToken(AddressTransaction)); transactionReceiptRepository = app.get>(getRepositoryToken(TransactionReceipt)); diff --git a/packages/api/test/block-api.e2e-spec.ts b/packages/api/test/block-api.e2e-spec.ts index dab94a051e..c04f833be8 100644 --- a/packages/api/test/block-api.e2e-spec.ts +++ b/packages/api/test/block-api.e2e-spec.ts @@ -5,12 +5,12 @@ import { Repository } from "typeorm"; import { getRepositoryToken } from "@nestjs/typeorm"; import { AppModule } from "../src/app.module"; import { configureApp } from "../src/configureApp"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { BatchDetails } from "../src/batch/batchDetails.entity"; describe("Block API (e2e)", () => { let app: INestApplication; - let blockRepository: Repository; + let blockRepository: Repository; let batchRepository: Repository; beforeAll(async () => { @@ -24,7 +24,7 @@ describe("Block API (e2e)", () => { await app.init(); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); batchRepository = app.get>(getRepositoryToken(BatchDetails)); for (let i = 0; i < 9; i++) { diff --git a/packages/api/test/block.e2e-spec.ts b/packages/api/test/block.e2e-spec.ts index c2bcf91129..3cf78d81e8 100644 --- a/packages/api/test/block.e2e-spec.ts +++ b/packages/api/test/block.e2e-spec.ts @@ -5,12 +5,12 @@ import { Repository } from "typeorm"; import { getRepositoryToken } from "@nestjs/typeorm"; import { AppModule } from "../src/app.module"; import { configureApp } from "../src/configureApp"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { BatchDetails } from "../src/batch/batchDetails.entity"; describe("BlockController (e2e)", () => { let app: INestApplication; - let blockRepository: Repository; + let blockRepository: Repository; let batchRepository: Repository; beforeAll(async () => { @@ -24,7 +24,7 @@ describe("BlockController (e2e)", () => { await app.init(); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); batchRepository = app.get>(getRepositoryToken(BatchDetails)); for (let i = 0; i < 9; i++) { diff --git a/packages/api/test/log-api.e2e-spec.ts b/packages/api/test/log-api.e2e-spec.ts index f81f801a1e..ad04223e9e 100644 --- a/packages/api/test/log-api.e2e-spec.ts +++ b/packages/api/test/log-api.e2e-spec.ts @@ -4,7 +4,7 @@ import { getRepositoryToken } from "@nestjs/typeorm"; import * as request from "supertest"; import { Repository } from "typeorm"; import { BatchDetails } from "../src/batch/batchDetails.entity"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { Log } from "../src/log/log.entity"; import { Transaction } from "../src/transaction/entities/transaction.entity"; import { TransactionReceipt } from "../src/transaction/entities/transactionReceipt.entity"; @@ -15,7 +15,7 @@ describe("Logs API (e2e)", () => { let app: INestApplication; let transactionRepository: Repository; let transactionReceiptRepository: Repository; - let blockRepository: Repository; + let blockRepository: Repository; let batchRepository: Repository; let logRepository: Repository; @@ -30,7 +30,7 @@ describe("Logs API (e2e)", () => { transactionRepository = app.get>(getRepositoryToken(Transaction)); transactionReceiptRepository = app.get>(getRepositoryToken(TransactionReceipt)); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); batchRepository = app.get>(getRepositoryToken(BatchDetails)); logRepository = app.get>(getRepositoryToken(Log)); diff --git a/packages/api/test/stats-api.e2e-spec.ts b/packages/api/test/stats-api.e2e-spec.ts index d9fec2f445..c805e8648c 100644 --- a/packages/api/test/stats-api.e2e-spec.ts +++ b/packages/api/test/stats-api.e2e-spec.ts @@ -4,14 +4,14 @@ import { getRepositoryToken } from "@nestjs/typeorm"; import * as request from "supertest"; import { Repository } from "typeorm"; import { BatchDetails } from "../src/batch/batchDetails.entity"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { Token, ETH_TOKEN } from "../src/token/token.entity"; import { AppModule } from "../src/app.module"; import { configureApp } from "../src/configureApp"; describe("Stats API (e2e)", () => { let app: INestApplication; - let blockRepository: Repository; + let blockRepository: Repository; let batchRepository: Repository; let tokenRepository: Repository; @@ -24,7 +24,7 @@ describe("Stats API (e2e)", () => { configureApp(app); await app.init(); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); batchRepository = app.get>(getRepositoryToken(BatchDetails)); tokenRepository = app.get>(getRepositoryToken(Token)); diff --git a/packages/api/test/stats.e2e-spec.ts b/packages/api/test/stats.e2e-spec.ts index 291ce881dc..4666a6e379 100644 --- a/packages/api/test/stats.e2e-spec.ts +++ b/packages/api/test/stats.e2e-spec.ts @@ -6,14 +6,14 @@ import { getRepositoryToken } from "@nestjs/typeorm"; import { AppModule } from "../src/app.module"; import { configureApp } from "../src/configureApp"; import { BatchDetails } from "../src/batch/batchDetails.entity"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { Transaction } from "../src/transaction/entities/transaction.entity"; import { Counter } from "../src/counter/counter.entity"; describe("StatsController (e2e)", () => { let app: INestApplication; let batchRepository: Repository; - let blockRepository: Repository; + let blockRepository: Repository; let transactionRepository: Repository; let counterRepository: Repository; @@ -29,7 +29,7 @@ describe("StatsController (e2e)", () => { await app.init(); batchRepository = app.get>(getRepositoryToken(BatchDetails)); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); transactionRepository = app.get>(getRepositoryToken(Transaction)); counterRepository = app.get>(getRepositoryToken(Counter)); diff --git a/packages/api/test/token-api.e2e-spec.ts b/packages/api/test/token-api.e2e-spec.ts index ce825b8535..f3735bc8e2 100644 --- a/packages/api/test/token-api.e2e-spec.ts +++ b/packages/api/test/token-api.e2e-spec.ts @@ -4,14 +4,14 @@ import { getRepositoryToken } from "@nestjs/typeorm"; import * as request from "supertest"; import { Repository } from "typeorm"; import { BatchDetails } from "../src/batch/batchDetails.entity"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { Token, ETH_TOKEN } from "../src/token/token.entity"; import { AppModule } from "../src/app.module"; import { configureApp } from "../src/configureApp"; describe("Token API (e2e)", () => { let app: INestApplication; - let blockRepository: Repository; + let blockRepository: Repository; let batchRepository: Repository; let tokenRepository: Repository; @@ -24,7 +24,7 @@ describe("Token API (e2e)", () => { configureApp(app); await app.init(); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); batchRepository = app.get>(getRepositoryToken(BatchDetails)); tokenRepository = app.get>(getRepositoryToken(Token)); diff --git a/packages/api/test/token.e2e-spec.ts b/packages/api/test/token.e2e-spec.ts index 3ca44fae97..b307db3298 100644 --- a/packages/api/test/token.e2e-spec.ts +++ b/packages/api/test/token.e2e-spec.ts @@ -6,7 +6,7 @@ import { getRepositoryToken } from "@nestjs/typeorm"; import { AppModule } from "../src/app.module"; import { configureApp } from "../src/configureApp"; import { Token, TokenType, ETH_TOKEN } from "../src/token/token.entity"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { Transaction } from "../src/transaction/entities/transaction.entity"; import { Transfer, TransferType } from "../src/transfer/transfer.entity"; import { BatchDetails } from "../src/batch/batchDetails.entity"; @@ -14,7 +14,7 @@ import { BatchDetails } from "../src/batch/batchDetails.entity"; describe("TokenController (e2e)", () => { let app: INestApplication; let tokenRepository: Repository; - let blockRepository: Repository; + let blockRepository: Repository; let transactionRepository: Repository; let transferRepository: Repository; let batchRepository: Repository; @@ -31,7 +31,7 @@ describe("TokenController (e2e)", () => { await app.init(); tokenRepository = app.get>(getRepositoryToken(Token)); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); transactionRepository = app.get>(getRepositoryToken(Transaction)); transferRepository = app.get>(getRepositoryToken(Transfer)); batchRepository = app.get>(getRepositoryToken(BatchDetails)); diff --git a/packages/api/test/transaction-api.e2e-spec.ts b/packages/api/test/transaction-api.e2e-spec.ts index 387de4330b..4fb83e5454 100644 --- a/packages/api/test/transaction-api.e2e-spec.ts +++ b/packages/api/test/transaction-api.e2e-spec.ts @@ -4,7 +4,7 @@ import { getRepositoryToken } from "@nestjs/typeorm"; import * as request from "supertest"; import { Repository } from "typeorm"; import { BatchDetails } from "../src/batch/batchDetails.entity"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { Transaction } from "../src/transaction/entities/transaction.entity"; import { TransactionReceipt } from "../src/transaction/entities/transactionReceipt.entity"; import { AppModule } from "../src/app.module"; @@ -14,7 +14,7 @@ describe("Transaction API (e2e)", () => { let app: INestApplication; let transactionRepository: Repository; let transactionReceiptRepository: Repository; - let blockRepository: Repository; + let blockRepository: Repository; let batchRepository: Repository; beforeAll(async () => { @@ -28,7 +28,7 @@ describe("Transaction API (e2e)", () => { transactionRepository = app.get>(getRepositoryToken(Transaction)); transactionReceiptRepository = app.get>(getRepositoryToken(TransactionReceipt)); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); batchRepository = app.get>(getRepositoryToken(BatchDetails)); await batchRepository.insert({ diff --git a/packages/api/test/transaction.e2e-spec.ts b/packages/api/test/transaction.e2e-spec.ts index 37003bac45..e38111a34d 100644 --- a/packages/api/test/transaction.e2e-spec.ts +++ b/packages/api/test/transaction.e2e-spec.ts @@ -2,12 +2,14 @@ import { Test, TestingModule } from "@nestjs/testing"; import { INestApplication } from "@nestjs/common"; import * as request from "supertest"; import { Repository } from "typeorm"; +import { BigNumber } from "ethers"; import { getRepositoryToken } from "@nestjs/typeorm"; import { AppModule } from "../src/app.module"; import { configureApp } from "../src/configureApp"; import { Token, TokenType } from "../src/token/token.entity"; -import { BlockDetail } from "../src/block/blockDetail.entity"; +import { BlockDetails } from "../src/block/blockDetails.entity"; import { Transaction } from "../src/transaction/entities/transaction.entity"; +import { TransactionReceipt } from "../src/transaction/entities/transactionReceipt.entity"; import { ETH_TOKEN } from "../src/token/token.entity"; import { AddressTransaction } from "../src/transaction/entities/addressTransaction.entity"; import { Transfer, TransferType } from "../src/transfer/transfer.entity"; @@ -17,8 +19,9 @@ import { BatchDetails } from "../src/batch/batchDetails.entity"; describe("TransactionController (e2e)", () => { let app: INestApplication; let tokenRepository: Repository; - let blockRepository: Repository; + let blockRepository: Repository; let transactionRepository: Repository; + let transactionReceiptRepository: Repository; let addressTransactionRepository: Repository; let transferRepository: Repository; let logRepository: Repository; @@ -36,8 +39,9 @@ describe("TransactionController (e2e)", () => { await app.init(); tokenRepository = app.get>(getRepositoryToken(Token)); - blockRepository = app.get>(getRepositoryToken(BlockDetail)); + blockRepository = app.get>(getRepositoryToken(BlockDetails)); transactionRepository = app.get>(getRepositoryToken(Transaction)); + transactionReceiptRepository = app.get>(getRepositoryToken(TransactionReceipt)); addressTransactionRepository = app.get>(getRepositoryToken(AddressTransaction)); transferRepository = app.get>(getRepositoryToken(Transfer)); logRepository = app.get>(getRepositoryToken(Log)); @@ -125,6 +129,11 @@ describe("TransactionController (e2e)", () => { receivedAt: `2022-11-21T18:16:0${i}.000Z`, l1BatchNumber: i < 3 ? 1 : i, receiptStatus: i < 9 ? 1 : 0, + gasPrice: BigNumber.from(1000 + i).toString(), + gasLimit: BigNumber.from(2000 + i).toString(), + maxFeePerGas: BigNumber.from(3000 + i).toString(), + maxPriorityFeePerGas: BigNumber.from(4000 + i).toString(), + gasPerPubdata: BigNumber.from(5000 + i).toHexString(), }; await transactionRepository.insert(transactionSpec); @@ -137,6 +146,14 @@ describe("TransactionController (e2e)", () => { transactionIndex: transactionSpec.transactionIndex, }); } + + await transactionReceiptRepository.insert({ + transactionHash: transactionSpec.hash, + from: transactionSpec.from, + status: 1, + gasUsed: (7000 + i).toString(), + cumulativeGasUsed: (10000 + i).toString(), + }); } for (let i = 0; i < 20; i++) { @@ -208,6 +225,7 @@ describe("TransactionController (e2e)", () => { await tokenRepository.delete({}); await addressTransactionRepository.delete({}); await transactionRepository.delete({}); + await transactionReceiptRepository.delete({}); await blockRepository.delete({}); await batchRepository.delete({}); @@ -241,8 +259,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2009", + gasPrice: "1009", + gasPerPubdata: "5009", + maxFeePerGas: "3009", + maxPriorityFeePerGas: "4009", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e19", isL1BatchSealed: false, isL1Originated: true, @@ -264,8 +285,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab8", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2008", + gasPrice: "1008", + gasPerPubdata: "5008", + maxFeePerGas: "3008", + maxPriorityFeePerGas: "4008", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e18", isL1BatchSealed: true, isL1Originated: true, @@ -287,8 +311,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab7", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2007", + gasPrice: "1007", + gasPerPubdata: "5007", + maxFeePerGas: "3007", + maxPriorityFeePerGas: "4007", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e17", isL1BatchSealed: true, isL1Originated: true, @@ -310,8 +337,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2006", + gasPrice: "1006", + gasPerPubdata: "5006", + maxFeePerGas: "3006", + maxPriorityFeePerGas: "4006", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e16", isL1BatchSealed: true, isL1Originated: true, @@ -333,8 +363,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2005", + gasPrice: "1005", + gasPerPubdata: "5005", + maxFeePerGas: "3005", + maxPriorityFeePerGas: "4005", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e15", isL1BatchSealed: true, isL1Originated: true, @@ -356,8 +389,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasPrice: "1004", + gasLimit: "2004", + gasPerPubdata: "5004", + maxFeePerGas: "3004", + maxPriorityFeePerGas: "4004", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e14", isL1BatchSealed: true, isL1Originated: true, @@ -379,8 +415,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2003", + gasPrice: "1003", + gasPerPubdata: "5003", + maxFeePerGas: "3003", + maxPriorityFeePerGas: "4003", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e13", isL1BatchSealed: true, isL1Originated: true, @@ -402,8 +441,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasPrice: "1002", + gasLimit: "2002", + gasPerPubdata: "5002", + maxFeePerGas: "3002", + maxPriorityFeePerGas: "4002", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e12", isL1BatchSealed: false, isL1Originated: true, @@ -425,8 +467,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasPrice: "1001", + gasLimit: "2001", + gasPerPubdata: "5001", + maxFeePerGas: "3001", + maxPriorityFeePerGas: "4001", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e11", isL1BatchSealed: false, isL1Originated: true, @@ -448,8 +493,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2000", + gasPrice: "1000", + gasPerPubdata: "5000", + maxFeePerGas: "3000", + maxPriorityFeePerGas: "4000", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10", isL1BatchSealed: false, isL1Originated: true, @@ -481,8 +529,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab8", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2008", + gasPrice: "1008", + gasPerPubdata: "5008", + maxFeePerGas: "3008", + maxPriorityFeePerGas: "4008", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e18", isL1BatchSealed: true, isL1Originated: true, @@ -504,8 +555,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab7", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2007", + gasPrice: "1007", + gasPerPubdata: "5007", + maxFeePerGas: "3007", + maxPriorityFeePerGas: "4007", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e17", isL1BatchSealed: true, isL1Originated: true, @@ -527,8 +581,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2006", + gasPrice: "1006", + gasPerPubdata: "5006", + maxFeePerGas: "3006", + maxPriorityFeePerGas: "4006", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e16", isL1BatchSealed: true, isL1Originated: true, @@ -606,8 +663,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2001", + gasPrice: "1001", + gasPerPubdata: "5001", + maxFeePerGas: "3001", + maxPriorityFeePerGas: "4001", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e11", isL1BatchSealed: false, isL1Originated: true, @@ -654,8 +714,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2001", + gasPrice: "1001", + gasPerPubdata: "5001", + maxFeePerGas: "3001", + maxPriorityFeePerGas: "4001", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e11", isL1BatchSealed: false, isL1Originated: true, @@ -702,8 +765,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab7", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2007", + gasPrice: "1007", + gasPerPubdata: "5007", + maxFeePerGas: "3007", + maxPriorityFeePerGas: "4007", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e17", isL1BatchSealed: true, isL1Originated: true, @@ -725,8 +791,11 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2006", + gasPrice: "1006", + gasPerPubdata: "5006", + maxFeePerGas: "3006", + maxPriorityFeePerGas: "4006", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e16", isL1BatchSealed: true, isL1Originated: true, @@ -809,8 +878,12 @@ describe("TransactionController (e2e)", () => { executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab8", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2008", + gasPrice: "1008", + gasUsed: "7008", + gasPerPubdata: "5008", + maxFeePerGas: "3008", + maxPriorityFeePerGas: "4008", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e18", isL1BatchSealed: true, isL1Originated: true, @@ -840,8 +913,12 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2005", + gasPrice: "1005", + gasUsed: "7005", + gasPerPubdata: "5005", + maxFeePerGas: "3005", + maxPriorityFeePerGas: "4005", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e15", isL1BatchSealed: true, isL1Originated: true, @@ -871,8 +948,12 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2003", + gasPrice: "1003", + gasUsed: "7003", + gasPerPubdata: "5003", + maxFeePerGas: "3003", + maxPriorityFeePerGas: "4003", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e13", isL1BatchSealed: true, isL1Originated: true, @@ -902,8 +983,12 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2000", + gasPrice: "1000", + gasUsed: "7000", + gasPerPubdata: "5000", + maxFeePerGas: "3000", + maxPriorityFeePerGas: "4000", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10", isL1BatchSealed: true, isL1Originated: true, @@ -933,8 +1018,12 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2009", + gasPrice: "1009", + gasUsed: "7009", + gasPerPubdata: "5009", + maxFeePerGas: "3009", + maxPriorityFeePerGas: "4009", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e19", isL1BatchSealed: true, isL1Originated: true, @@ -964,8 +1053,12 @@ describe("TransactionController (e2e)", () => { executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", - gasLimit: "1000000", - gasPrice: "100", + gasLimit: "2000", + gasPrice: "1000", + gasUsed: "7000", + gasPerPubdata: "5000", + maxFeePerGas: "3000", + maxPriorityFeePerGas: "4000", hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10", isL1BatchSealed: true, isL1Originated: true, From 845500d2a1d28294ebd6ea1f2e6bbce3e956baad Mon Sep 17 00:00:00 2001 From: Roman Petriv Date: Tue, 5 Dec 2023 15:47:07 +0200 Subject: [PATCH 2/4] feat: add transaction error and revert reason (#110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ - fetch and store tx error and revert reason; - return tx error and revert reason via API; - display tx error on UI; ## Why ❔ - fast way to see what's the reason of transaction failure; ## Checklist - [+] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [+] Tests for the changes have been added / updated. - [+] Documentation comments have been added / updated. --- .../transaction.controller.spec.ts | 50 +++++++ .../api/transaction/transaction.controller.ts | 2 +- .../src/transaction/dtos/transaction.dto.ts | 18 +++ .../entities/transaction.entity.ts | 6 + packages/api/test/transaction.e2e-spec.ts | 46 ++++++ .../transactions/infoTable/GeneralInfo.vue | 19 ++- packages/app/src/composables/common/Api.d.ts | 2 + .../app/src/composables/useTransaction.ts | 4 + packages/app/src/locales/en.json | 2 + packages/app/src/locales/uk.json | 3 + .../transactions/GeneralInfo.spec.ts | 4 +- .../components/transactions/Table.spec.ts | 2 + .../tests/composables/useTransaction.spec.ts | 4 + .../tests/composables/useTransactions.spec.ts | 2 + .../src/blockchain/blockchain.service.spec.ts | 140 ++++++++++++++++++ .../src/blockchain/blockchain.service.ts | 20 +++ .../worker/src/entities/transaction.entity.ts | 6 + .../1700684231991-AddTransactionError.ts | 15 ++ .../repositories/transaction.repository.ts | 2 + .../transaction/transaction.processor.spec.ts | 52 ++++++- .../src/transaction/transaction.processor.ts | 24 ++- 21 files changed, 411 insertions(+), 12 deletions(-) create mode 100644 packages/worker/src/migrations/1700684231991-AddTransactionError.ts diff --git a/packages/api/src/api/transaction/transaction.controller.spec.ts b/packages/api/src/api/transaction/transaction.controller.spec.ts index a74a252aa0..049fec6b5e 100644 --- a/packages/api/src/api/transaction/transaction.controller.spec.ts +++ b/packages/api/src/api/transaction/transaction.controller.spec.ts @@ -85,6 +85,56 @@ describe("TransactionController", () => { }, }); }); + + it("returns transaction error in errDescription when transaction is failed and transaction error is present", async () => { + jest.spyOn(transactionServiceMock, "findOne").mockResolvedValue({ + status: TransactionStatus.Failed, + error: "Error", + revertReason: "Reverted", + } as TransactionDetails); + + const response = await controller.getTransactionStatus(transactionHash); + expect(response).toEqual({ + status: ResponseStatus.OK, + message: ResponseMessage.OK, + result: { + isError: "1", + errDescription: "Error", + }, + }); + }); + + it("returns transaction revert reason in errDescription when transaction is failed and transaction revert reason is present", async () => { + jest + .spyOn(transactionServiceMock, "findOne") + .mockResolvedValue({ status: TransactionStatus.Failed, revertReason: "Reverted" } as TransactionDetails); + + const response = await controller.getTransactionStatus(transactionHash); + expect(response).toEqual({ + status: ResponseStatus.OK, + message: ResponseMessage.OK, + result: { + isError: "1", + errDescription: "Reverted", + }, + }); + }); + + it("returns empty errDescription when transaction is failed and transaction error and revert reason are not present", async () => { + jest + .spyOn(transactionServiceMock, "findOne") + .mockResolvedValue({ status: TransactionStatus.Failed } as TransactionDetails); + + const response = await controller.getTransactionStatus(transactionHash); + expect(response).toEqual({ + status: ResponseStatus.OK, + message: ResponseMessage.OK, + result: { + isError: "1", + errDescription: "", + }, + }); + }); }); describe("getTransactionReceiptStatus", () => { diff --git a/packages/api/src/api/transaction/transaction.controller.ts b/packages/api/src/api/transaction/transaction.controller.ts index 29d993b78d..cede0e0c59 100644 --- a/packages/api/src/api/transaction/transaction.controller.ts +++ b/packages/api/src/api/transaction/transaction.controller.ts @@ -36,7 +36,7 @@ export class TransactionController { message: ResponseMessage.OK, result: { isError: hasError ? ResponseStatus.OK : ResponseStatus.NOTOK, - errDescription: "", + errDescription: transaction?.error || transaction?.revertReason || "", }, }; } diff --git a/packages/api/src/transaction/dtos/transaction.dto.ts b/packages/api/src/transaction/dtos/transaction.dto.ts index 8b77f54a1d..597ec07753 100644 --- a/packages/api/src/transaction/dtos/transaction.dto.ts +++ b/packages/api/src/transaction/dtos/transaction.dto.ts @@ -186,4 +186,22 @@ export class TransactionDto { examples: ["included", "committed", "proved", "verified", "failed"], }) public readonly status: TransactionStatus; + + @ApiProperty({ + type: String, + description: "Transaction error", + example: "Some test error", + examples: ["Some test error", null], + nullable: true, + }) + public readonly error?: string; + + @ApiProperty({ + type: String, + description: "Transaction revert reason", + example: "Some test revert reason", + examples: ["Some test revert reason", null], + nullable: true, + }) + public readonly revertReason?: string; } diff --git a/packages/api/src/transaction/entities/transaction.entity.ts b/packages/api/src/transaction/entities/transaction.entity.ts index e4a1b357e8..b907d539c5 100644 --- a/packages/api/src/transaction/entities/transaction.entity.ts +++ b/packages/api/src/transaction/entities/transaction.entity.ts @@ -101,6 +101,12 @@ export class Transaction extends BaseEntity { @OneToMany(() => Transfer, (transfer) => transfer.transaction) public readonly transfers: Transfer[]; + @Column({ nullable: true }) + public readonly error?: string; + + @Column({ nullable: true }) + public readonly revertReason?: string; + public get status(): TransactionStatus { if (this.receiptStatus === 0) { return TransactionStatus.Failed; diff --git a/packages/api/test/transaction.e2e-spec.ts b/packages/api/test/transaction.e2e-spec.ts index e38111a34d..28f55e29f1 100644 --- a/packages/api/test/transaction.e2e-spec.ts +++ b/packages/api/test/transaction.e2e-spec.ts @@ -256,6 +256,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 9, commitTxHash: null, data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -282,6 +284,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 8, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa8", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab8", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -308,6 +312,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 7, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa7", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab7", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -334,6 +340,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 6, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa6", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -360,6 +368,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 5, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa5", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -386,6 +396,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 4, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa4", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -412,6 +424,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 3, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa3", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -438,6 +452,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 1, commitTxHash: null, data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -464,6 +480,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 1, commitTxHash: null, data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -490,6 +508,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 1, commitTxHash: null, data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -526,6 +546,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 8, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa8", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab8", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -552,6 +574,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 7, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa7", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab7", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -578,6 +602,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 6, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa6", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -660,6 +686,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 1, commitTxHash: null, data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -711,6 +739,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 1, commitTxHash: null, data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -762,6 +792,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 7, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa7", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab7", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -788,6 +820,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 6, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa6", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -875,6 +909,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 8, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa8", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5ab8", fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -910,6 +946,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 5, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa5", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -945,6 +983,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 3, commitTxHash: "0xeb5ead20476b91008c3b6e44005017e697de78e4fd868d99d2c58566655c5aa3", data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -980,6 +1020,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 1, commitTxHash: null, data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -1015,6 +1057,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 9, commitTxHash: null, data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -1050,6 +1094,8 @@ describe("TransactionController (e2e)", () => { blockNumber: 1, commitTxHash: null, data: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + error: null, + revertReason: null, executeTxHash: null, fee: "0x2386f26fc10000", from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", diff --git a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue index ef84402e10..9141e4769f 100644 --- a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue +++ b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue @@ -30,6 +30,19 @@ /> + + + + {{ t("transactions.table.reason") }} + + + {{ t("transactions.table.reasonTooltip") }} + + + + {{ transaction.error || transaction.revertReason || "" }} + + {{ t("transactions.table.block") }} @@ -233,9 +246,6 @@ const tokenTransfers = computed(() => { diff --git a/packages/app/src/composables/common/Api.d.ts b/packages/app/src/composables/common/Api.d.ts index 371236776a..736992256b 100644 --- a/packages/app/src/composables/common/Api.d.ts +++ b/packages/app/src/composables/common/Api.d.ts @@ -86,6 +86,8 @@ declare namespace Api { l1BatchNumber: number | null; isL1BatchSealed: boolean; status: "included" | "committed" | "proved" | "verified" | "failed"; + error: string | null; + revertReason: string | null; }; type Transfer = { diff --git a/packages/app/src/composables/useTransaction.ts b/packages/app/src/composables/useTransaction.ts index fe48c1da0f..f920b7689a 100644 --- a/packages/app/src/composables/useTransaction.ts +++ b/packages/app/src/composables/useTransaction.ts @@ -64,6 +64,8 @@ export type TransactionItem = { status: TransactionStatus; l1BatchNumber: number | null; isL1BatchSealed: boolean; + error?: string | null; + revertReason?: string | null; logs: TransactionLogEntry[]; transfers: TokenTransfer[]; }; @@ -222,6 +224,8 @@ export function mapTransaction( status: transaction.status, l1BatchNumber: transaction.l1BatchNumber, isL1BatchSealed: transaction.isL1BatchSealed, + error: transaction.error, + revertReason: transaction.revertReason, logs: logs.map((item) => ({ address: item.address, diff --git a/packages/app/src/locales/en.json b/packages/app/src/locales/en.json index b62ec696f3..c1dc5faa01 100644 --- a/packages/app/src/locales/en.json +++ b/packages/app/src/locales/en.json @@ -98,6 +98,8 @@ "table": { "status": "Status", "statusTooltip": "The status of the transaction", + "reason": "Reason", + "reasonTooltip": "The failure reason of the transaction", "txnHash": "Txn hash", "transactionHash": "Transaction Hash", "transactionHashTooltip": "Transaction hash is a unique 66-character identifier that is generated whenever a transaction is executed", diff --git a/packages/app/src/locales/uk.json b/packages/app/src/locales/uk.json index 57b0dd91d0..10df6b08ad 100644 --- a/packages/app/src/locales/uk.json +++ b/packages/app/src/locales/uk.json @@ -72,6 +72,9 @@ }, "table": { "status": "Статус", + "statusTooltip": "Статус транзакції", + "reason": "Причина", + "reasonTooltip": "Причина невиконання транзакції", "transactionHash": "Хеш Транзакції", "nonce": "Нонс", "created": "Створено", diff --git a/packages/app/tests/components/transactions/GeneralInfo.spec.ts b/packages/app/tests/components/transactions/GeneralInfo.spec.ts index 9b101af347..d24bf80f66 100644 --- a/packages/app/tests/components/transactions/GeneralInfo.spec.ts +++ b/packages/app/tests/components/transactions/GeneralInfo.spec.ts @@ -310,15 +310,17 @@ describe("Transaction info table", () => { plugins: [i18n, $testId], }, props: { - transaction: { ...transaction, status: "failed" }, + transaction: { ...transaction, status: "failed", revertReason: "Revert reason" }, loading: false, }, }); await nextTick(); const status = wrapper.findAll("tbody tr td:nth-child(2)")[1]; const badges = status.findAllComponents(Badge); + const reason = wrapper.find(".transaction-reason-value"); expect(badges.length).toBe(1); expect(badges[0].text()).toBe(i18n.global.t("transactions.statusComponent.failed")); + expect(reason.text()).toBe("Revert reason"); }); it("renders included transaction status", async () => { const wrapper = mount(Table, { diff --git a/packages/app/tests/components/transactions/Table.spec.ts b/packages/app/tests/components/transactions/Table.spec.ts index 50cc9d4000..afe9966596 100644 --- a/packages/app/tests/components/transactions/Table.spec.ts +++ b/packages/app/tests/components/transactions/Table.spec.ts @@ -57,6 +57,8 @@ const transaction: TransactionListItem = { executeTxHash: null, proveTxHash: null, isL1BatchSealed: false, + error: null, + revertReason: null, }; const contractAbi: AbiFragment[] = [ diff --git a/packages/app/tests/composables/useTransaction.spec.ts b/packages/app/tests/composables/useTransaction.spec.ts index 6ca5c1927e..a93a8f37fe 100644 --- a/packages/app/tests/composables/useTransaction.spec.ts +++ b/packages/app/tests/composables/useTransaction.spec.ts @@ -88,6 +88,8 @@ vi.mock("ohmyfetch", async () => { commitTxHash: "0xe6a7ed0b6bf1c49f27feae3a71e5ba2aa4abaa6e372524369529946eb61a6936", executeTxHash: "0xdd70c8c2f59d88b9970c3b48a1230320f051d4502d0277124db481a42ada5c33", proveTxHash: "0x688c20e2106984bb0ccdadecf01e7bf12088b0ba671d888eca8e577ceac0d790", + error: null, + revertReason: null, }; return { ...mod, @@ -444,6 +446,8 @@ describe("useTransaction:", () => { nonce: 24, receivedAt: "2023-02-28T08:42:08.198Z", status: "verified", + error: null, + revertReason: null, l1BatchNumber: 11014, isL1BatchSealed: true, logs: [ diff --git a/packages/app/tests/composables/useTransactions.spec.ts b/packages/app/tests/composables/useTransactions.spec.ts index bbc526e5fb..a5e3263953 100644 --- a/packages/app/tests/composables/useTransactions.spec.ts +++ b/packages/app/tests/composables/useTransactions.spec.ts @@ -27,6 +27,8 @@ const transaction: TransactionListItem = { executeTxHash: null, proveTxHash: null, isL1BatchSealed: false, + error: null, + revertReason: null, }; vi.mock("ohmyfetch", () => { diff --git a/packages/worker/src/blockchain/blockchain.service.spec.ts b/packages/worker/src/blockchain/blockchain.service.spec.ts index 5593bd3042..bacfa748b1 100644 --- a/packages/worker/src/blockchain/blockchain.service.spec.ts +++ b/packages/worker/src/blockchain/blockchain.service.spec.ts @@ -1471,6 +1471,146 @@ describe("BlockchainService", () => { }); }); + describe("debugTraceTransaction", () => { + const traceTransactionResult = { + type: "Call", + from: "0x0000000000000000000000000000000000000000", + to: "0x0000000000000000000000000000000000008001", + error: null, + revertReason: "Exceed daily limit", + }; + let timeoutSpy; + + beforeEach(() => { + jest.spyOn(provider, "send").mockResolvedValue(traceTransactionResult); + timeoutSpy = jest.spyOn(timersPromises, "setTimeout"); + }); + + it("starts the rpc call duration metric", async () => { + await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b" + ); + expect(startRpcCallDurationMetricMock).toHaveBeenCalledTimes(1); + }); + + it("gets transaction trace", async () => { + await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b" + ); + expect(provider.send).toHaveBeenCalledTimes(1); + expect(provider.send).toHaveBeenCalledWith("debug_traceTransaction", [ + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b", + { + tracer: "callTracer", + tracerConfig: { onlyTopCall: false }, + }, + ]); + }); + + it("gets transaction trace with only top call", async () => { + await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b", + true + ); + expect(provider.send).toHaveBeenCalledTimes(1); + expect(provider.send).toHaveBeenCalledWith("debug_traceTransaction", [ + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b", + { + tracer: "callTracer", + tracerConfig: { onlyTopCall: true }, + }, + ]); + }); + + it("stops the rpc call duration metric", async () => { + await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b" + ); + expect(stopRpcCallDurationMetricMock).toHaveBeenCalledTimes(1); + expect(stopRpcCallDurationMetricMock).toHaveBeenCalledWith({ function: "debugTraceTransaction" }); + }); + + it("returns transaction trace", async () => { + const result = await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b" + ); + expect(result).toEqual(traceTransactionResult); + }); + + describe("if the call throws an error", () => { + beforeEach(() => { + jest + .spyOn(provider, "send") + .mockRejectedValueOnce(new Error("RPC call error")) + .mockRejectedValueOnce(new Error("RPC call error")) + .mockResolvedValueOnce(traceTransactionResult); + }); + + it("retries RPC call with a default timeout", async () => { + await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b" + ); + expect(provider.send).toHaveBeenCalledTimes(3); + expect(timeoutSpy).toHaveBeenCalledTimes(2); + expect(timeoutSpy).toHaveBeenNthCalledWith(1, defaultRetryTimeout); + expect(timeoutSpy).toHaveBeenNthCalledWith(2, defaultRetryTimeout); + }); + + it("stops the rpc call duration metric only for the successful retry", async () => { + await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b" + ); + expect(stopRpcCallDurationMetricMock).toHaveBeenCalledTimes(1); + expect(stopRpcCallDurationMetricMock).toHaveBeenCalledWith({ function: "debugTraceTransaction" }); + }); + + it("returns result of the successful RPC call", async () => { + const result = await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b" + ); + expect(result).toEqual(traceTransactionResult); + }); + }); + + describe("if the call throws a timeout error", () => { + beforeEach(() => { + jest + .spyOn(provider, "send") + .mockRejectedValueOnce({ code: "TIMEOUT" }) + .mockRejectedValueOnce({ code: "TIMEOUT" }) + .mockResolvedValueOnce(traceTransactionResult); + }); + + it("retries RPC call with a quick timeout", async () => { + await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b" + ); + expect(timeoutSpy).toHaveBeenCalledTimes(2); + expect(timeoutSpy).toHaveBeenNthCalledWith(1, quickRetryTimeout); + expect(timeoutSpy).toHaveBeenNthCalledWith(2, quickRetryTimeout); + }); + }); + + describe("if the call throws a connection refused error", () => { + beforeEach(() => { + jest + .spyOn(provider, "send") + .mockRejectedValueOnce({ code: "TIMEOUT" }) + .mockRejectedValueOnce({ code: "TIMEOUT" }) + .mockResolvedValueOnce(traceTransactionResult); + }); + + it("retries RPC call with a quick timeout", async () => { + await blockchainService.debugTraceTransaction( + "0xc0ae49e96910fa9df22eb59c0977905864664d495bc95906120695aa26e1710b" + ); + expect(timeoutSpy).toHaveBeenCalledTimes(2); + expect(timeoutSpy).toHaveBeenNthCalledWith(1, quickRetryTimeout); + expect(timeoutSpy).toHaveBeenNthCalledWith(2, quickRetryTimeout); + }); + }); + }); + describe("onModuleInit", () => { let bridgeAddresses; beforeEach(() => { diff --git a/packages/worker/src/blockchain/blockchain.service.ts b/packages/worker/src/blockchain/blockchain.service.ts index cf6dbf64b8..741631c8d5 100644 --- a/packages/worker/src/blockchain/blockchain.service.ts +++ b/packages/worker/src/blockchain/blockchain.service.ts @@ -15,6 +15,14 @@ export interface BridgeAddresses { l2Erc20DefaultBridge: string; } +export interface TraceTransactionResult { + type: string; + from: string; + to: string; + error: string | null; + revertReason: string | null; +} + @Injectable() export class BlockchainService implements OnModuleInit { private readonly logger: Logger; @@ -121,6 +129,18 @@ export class BlockchainService implements OnModuleInit { }, "getDefaultBridgeAddresses"); } + public async debugTraceTransaction(txHash: string, onlyTopCall = false): Promise { + return await this.rpcCall(async () => { + return await this.provider.send("debug_traceTransaction", [ + txHash, + { + tracer: "callTracer", + tracerConfig: { onlyTopCall }, + }, + ]); + }, "debugTraceTransaction"); + } + public async on(eventName: EventType, listener: Listener): Promise { this.provider.on(eventName, listener); } diff --git a/packages/worker/src/entities/transaction.entity.ts b/packages/worker/src/entities/transaction.entity.ts index 0ec6d9b067..ca1f3511ec 100644 --- a/packages/worker/src/entities/transaction.entity.ts +++ b/packages/worker/src/entities/transaction.entity.ts @@ -90,4 +90,10 @@ export class Transaction extends CountableEntity { @Column({ type: "int", default: 1 }) public readonly receiptStatus: number; + + @Column({ nullable: true }) + public readonly error?: string; + + @Column({ nullable: true }) + public readonly revertReason?: string; } diff --git a/packages/worker/src/migrations/1700684231991-AddTransactionError.ts b/packages/worker/src/migrations/1700684231991-AddTransactionError.ts new file mode 100644 index 0000000000..c0ccce4efd --- /dev/null +++ b/packages/worker/src/migrations/1700684231991-AddTransactionError.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddTransactionError1700684231991 implements MigrationInterface { + name = "AddTransactionError1700684231991"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "transactions" ADD "error" character varying`); + await queryRunner.query(`ALTER TABLE "transactions" ADD "revertReason" character varying`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "transactions" DROP COLUMN "revertReason"`); + await queryRunner.query(`ALTER TABLE "transactions" DROP COLUMN "error"`); + } +} diff --git a/packages/worker/src/repositories/transaction.repository.ts b/packages/worker/src/repositories/transaction.repository.ts index b055c40769..610d1469be 100644 --- a/packages/worker/src/repositories/transaction.repository.ts +++ b/packages/worker/src/repositories/transaction.repository.ts @@ -10,6 +10,8 @@ export interface TransactionDto extends types.TransactionResponse { receiptStatus: number; isL1Originated: boolean; receivedAt: Date; + error?: string; + revertReason?: string; } @Injectable() diff --git a/packages/worker/src/transaction/transaction.processor.spec.ts b/packages/worker/src/transaction/transaction.processor.spec.ts index e152da109e..e6b28d30a2 100644 --- a/packages/worker/src/transaction/transaction.processor.spec.ts +++ b/packages/worker/src/transaction/transaction.processor.spec.ts @@ -3,7 +3,7 @@ import { Logger } from "@nestjs/common"; import { mock } from "jest-mock-extended"; import { types } from "zksync-web3"; import { TransactionRepository, TransactionReceiptRepository } from "../repositories"; -import { BlockchainService } from "../blockchain"; +import { BlockchainService, TraceTransactionResult } from "../blockchain"; import { TransactionProcessor } from "./transaction.processor"; import { LogProcessor } from "../log"; @@ -83,11 +83,16 @@ describe("TransactionProcessor", () => { status: 1, }); const transactionDetails = mock(); + const traceTransactionResult = mock({ + error: "Some error", + revertReason: "Some revert reason", + }); beforeEach(() => { jest.spyOn(blockchainServiceMock, "getTransaction").mockResolvedValue(transaction); jest.spyOn(blockchainServiceMock, "getTransactionReceipt").mockResolvedValue(transactionReceipt); jest.spyOn(blockchainServiceMock, "getTransactionDetails").mockResolvedValue(transactionDetails); + jest.spyOn(blockchainServiceMock, "debugTraceTransaction").mockResolvedValue(traceTransactionResult); }); it("starts the transaction duration metric", async () => { @@ -176,5 +181,50 @@ describe("TransactionProcessor", () => { await transactionProcessor.add(transaction.hash, blockDetails); expect(stopTxProcessingDurationMetricMock).toHaveBeenCalledTimes(1); }); + + describe("when transaction has failed status", () => { + beforeEach(() => { + (blockchainServiceMock.getTransactionReceipt as jest.Mock).mockResolvedValueOnce({ + transactionIndex: 0, + logs: [], + status: 0, + }); + }); + + it("reads transaction trace", async () => { + await transactionProcessor.add(transaction.hash, blockDetails); + expect(blockchainServiceMock.debugTraceTransaction).toHaveBeenCalledTimes(1); + expect(blockchainServiceMock.debugTraceTransaction).toHaveBeenCalledWith(transaction.hash, true); + }); + + describe("when transaction trace contains error and revert reason", () => { + it("adds the transaction info with error and revert reason", async () => { + await transactionProcessor.add(transaction.hash, blockDetails); + expect(transactionRepositoryMock.add).toHaveBeenCalledTimes(1); + expect(transactionRepositoryMock.add).toHaveBeenCalledWith({ + ...transaction, + ...transactionDetails, + l1BatchNumber: blockDetails.l1BatchNumber, + receiptStatus: 0, + error: traceTransactionResult.error, + revertReason: traceTransactionResult.revertReason, + }); + }); + }); + + describe("when transaction trace doe not contain error and revert reason", () => { + it("adds the transaction info without error and revert reason", async () => { + (blockchainServiceMock.debugTraceTransaction as jest.Mock).mockResolvedValueOnce(null); + await transactionProcessor.add(transaction.hash, blockDetails); + expect(transactionRepositoryMock.add).toHaveBeenCalledTimes(1); + expect(transactionRepositoryMock.add).toHaveBeenCalledWith({ + ...transaction, + ...transactionDetails, + l1BatchNumber: blockDetails.l1BatchNumber, + receiptStatus: 0, + }); + }); + }); + }); }); }); diff --git a/packages/worker/src/transaction/transaction.processor.ts b/packages/worker/src/transaction/transaction.processor.ts index 6a9f352efb..0f45a00f54 100644 --- a/packages/worker/src/transaction/transaction.processor.ts +++ b/packages/worker/src/transaction/transaction.processor.ts @@ -44,17 +44,29 @@ export class TransactionProcessor { throw new Error(`Some of the blockchain transaction APIs returned null for a transaction ${transactionHash}`); } + const transactionToAdd = { + ...transaction, + ...transactionDetails, + l1BatchNumber: blockDetails.l1BatchNumber, + receiptStatus: transactionReceipt.status, + } as TransactionDto; + + if (transactionReceipt.status === 0) { + const debugTraceTransactionResult = await this.blockchainService.debugTraceTransaction(transactionHash, true); + if (debugTraceTransactionResult?.error) { + transactionToAdd.error = debugTraceTransactionResult.error; + } + if (debugTraceTransactionResult?.revertReason) { + transactionToAdd.revertReason = debugTraceTransactionResult.revertReason; + } + } + this.logger.debug({ message: "Adding transaction data to the DB", blockNumber: blockDetails.number, transactionHash, }); - await this.transactionRepository.add({ - ...transaction, - ...transactionDetails, - l1BatchNumber: blockDetails.l1BatchNumber, - receiptStatus: transactionReceipt.status, - } as TransactionDto); + await this.transactionRepository.add(transactionToAdd); this.logger.debug({ message: "Adding transaction receipt data to the DB", From aacfa469451e8c6431f9cd4fc80f693f9dddef2d Mon Sep 17 00:00:00 2001 From: Vasyl Ivanchuk Date: Tue, 5 Dec 2023 20:30:13 +0200 Subject: [PATCH 3/4] fix: add sepolia staging hostname (#111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ Sepolia staging hostname. - [X] PR title corresponds to the body of PR (we generate changelog entries from PRs). --- packages/app/src/configs/staging.config.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/app/src/configs/staging.config.json b/packages/app/src/configs/staging.config.json index 7be5e57441..254c3f0859 100644 --- a/packages/app/src/configs/staging.config.json +++ b/packages/app/src/configs/staging.config.json @@ -21,7 +21,9 @@ "apiUrl": "https://block-explorer-api.sepolia.zksync.dev", "verificationApiUrl": "https://explorer.sepolia.era.zksync.dev", "bridgeUrl": "https://staging.goerli.bridge.zksync.dev", - "hostnames": [], + "hostnames": [ + "https://sepolia.staging-scan-v2.zksync.dev" + ], "icon": "/images/icons/zksync-arrows.svg", "l1ExplorerUrl": "https://sepolia.etherscan.io", "l2ChainId": 300, From 65d0a8d3d91643d32cdc680f91df95628e0fb376 Mon Sep 17 00:00:00 2001 From: Roman Petriv Date: Wed, 6 Dec 2023 15:23:02 +0200 Subject: [PATCH 4/4] chore: readme fixes (#113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ - remove $ from copy-pasteable commands - add sepolia links --- README.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f8b7ec1d91..e9276bde56 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ flowchart ## 🛠 Installation ```bash -$ npm install +npm install ``` ## ⚙️ Setting up env variables @@ -63,7 +63,7 @@ Make sure you have [zksync-era](https://github.com/matter-labs/zksync-era) repo The following script sets `.env` files for [Worker](./packages/worker) and [API](./packages/api) packages as well as environment configuration file for [App](./packages/app) package based on your local [zksync-era](https://github.com/matter-labs/zksync-era) repo setup. ```bash -$ npm run hyperchain:configure +npm run hyperchain:configure ``` You can review and edit generated files if you need to change any settings. @@ -72,18 +72,18 @@ You can review and edit generated files if you need to change any settings. Before running the solution, make sure you have a database server up and running, you have created a database and set up all the required environment variables. To create a database run the following command: ```bash -$ npm run db:create +npm run db:create ``` To run all the packages (`Worker`, `API` and front-end `App`) in `development` mode run the following command from the root directory. ```bash -$ npm run dev +npm run dev ``` For `production` mode run: ```bash -$ npm run build -$ npm run start +npm run build +npm run start ``` Each component can also be started individually. Follow individual packages `README` for details. @@ -105,15 +105,15 @@ To verify front-end `App` is running open http://localhost:3010 in your browser. ## 🕵️‍♂️ Testing Run unit tests for all packages: ```bash -$ npm run test +npm run test ``` Run e2e tests for all packages: ```bash -$ npm run test:e2e +npm run test:e2e ``` Run tests for a specific package: ```bash -$ npm run test -w {package} +npm run test -w {package} ``` For more details on testing please check individual packages `README`. @@ -129,7 +129,9 @@ zkSync Era Block Explorer is distributed under the terms of either at your option. ## 🔗 Production links -- Testnet API: https://block-explorer-api.testnets.zksync.dev +- Testnet Goerli API: https://block-explorer-api.testnets.zksync.dev +- Testnet Sepolia API: https://block-explorer-api.sepolia.zksync.dev - Mainnet API: https://block-explorer-api.mainnet.zksync.io -- Testnet App: https://goerli.explorer.zksync.io +- Testnet Goerli App: https://goerli.explorer.zksync.io +- Testnet Sepolia App: https://sepolia.explorer.zksync.io - Mainnet App: https://explorer.zksync.io