diff --git a/.changeset/nine-trees-eat.md b/.changeset/nine-trees-eat.md new file mode 100644 index 000000000..651389818 --- /dev/null +++ b/.changeset/nine-trees-eat.md @@ -0,0 +1,5 @@ +--- +"@blobscan/api": patch +--- + +Optimized element count performance by retrieving pre-calculated values from the stats table when filtering by rollup, date, or with no filters applied diff --git a/packages/api/src/middlewares/withFilters.ts b/packages/api/src/middlewares/withFilters.ts index 9712dd51c..c5514fbb3 100644 --- a/packages/api/src/middlewares/withFilters.ts +++ b/packages/api/src/middlewares/withFilters.ts @@ -1,5 +1,5 @@ import type { Prisma } from "@blobscan/db"; -import type { Rollup } from "@blobscan/db/prisma/enums"; +import type { Category, Rollup } from "@blobscan/db/prisma/enums"; import { z } from "@blobscan/zod"; import { t } from "../trpc-client"; @@ -26,8 +26,8 @@ export type Filters = Partial<{ blockSlot: NumberRange; blockType: Prisma.TransactionForkListRelationFilter; transactionAddresses: Prisma.TransactionWhereInput["OR"]; - transactionCategory: Prisma.TransactionWhereInput["category"]; - transactionRollup: Prisma.TransactionWhereInput["rollup"]; + transactionCategory: Category; + transactionRollup: Rollup | null; sort: Prisma.SortOrder; }>; diff --git a/packages/api/src/routers/blob/getCount.ts b/packages/api/src/routers/blob/getCount.ts index baa5197f6..e719e94eb 100644 --- a/packages/api/src/routers/blob/getCount.ts +++ b/packages/api/src/routers/blob/getCount.ts @@ -3,11 +3,11 @@ import { z } from "@blobscan/zod"; import type { Filters } from "../../middlewares/withFilters"; import { - hasCustomFilters, withAllFiltersSchema, withFilters, } from "../../middlewares/withFilters"; import { publicProcedure } from "../../procedures"; +import { buildStatsWhereClause, requiresDirectCount } from "../../utils/count"; const inputSchema = withAllFiltersSchema; @@ -15,24 +15,22 @@ const outputSchema = z.object({ totalBlobs: z.number(), }); +/** + * Counts blobs based on the provided filters. + * + * This function decides between counting blobs directly from the blob table + * or using pre-calculated aggregated data from daily or overall blob stats + * to improve performance. + * + * The choice depends on the specificity of the filters provided. + */ export async function countBlobs( prisma: BlobscanPrismaClient, filters: Filters ) { - if (!hasCustomFilters(filters)) { - const overallStats = await prisma.blobOverallStats.findFirst({ - select: { - totalBlobs: true, - }, - where: { - AND: [{ category: null }, { rollup: null }], - }, - }); - - return overallStats?.totalBlobs ?? 0; - } - const { + blockNumber, + blockTimestamp, transactionAddresses, transactionCategory, transactionRollup, @@ -40,31 +38,52 @@ export async function countBlobs( blockType, } = filters; - const txFiltersExists = - transactionRollup !== undefined || - transactionAddresses || - transactionCategory; - const blockFiltersExists = blockSlot || blockType; + if (requiresDirectCount(filters)) { + const transactionFiltersEnabled = + transactionCategory || transactionRollup || transactionAddresses; - return prisma.blobsOnTransactions.count({ - where: { - blockNumber: filters.blockNumber, - blockTimestamp: filters.blockTimestamp, - block: blockFiltersExists - ? { - slot: filters.blockSlot, - transactionForks: filters.blockType, - } - : undefined, - transaction: txFiltersExists - ? { - category: filters.transactionCategory, - rollup: filters.transactionRollup, - OR: filters.transactionAddresses, - } - : undefined, + return prisma.blobsOnTransactions.count({ + where: { + blockNumber, + blockTimestamp, + block: { + slot: blockSlot, + transactionForks: blockType, + }, + transaction: transactionFiltersEnabled + ? { + category: transactionCategory, + rollup: transactionRollup, + OR: transactionAddresses, + } + : undefined, + }, + }); + } + + const where = buildStatsWhereClause(filters); + + // Get count by summing daily total transaction stats data if a date range is provided in filters + if (filters.blockTimestamp) { + const dailyStats = await prisma.blobDailyStats.findMany({ + select: { + day: true, + totalBlobs: true, + }, + where, + }); + + return dailyStats.reduce((acc, { totalBlobs }) => acc + totalBlobs, 0); + } + + const overallStats = await prisma.blobOverallStats.findFirst({ + select: { + totalBlobs: true, }, + where, }); + + return overallStats?.totalBlobs ?? 0; } export const getCount = publicProcedure diff --git a/packages/api/src/routers/tx/getCount.ts b/packages/api/src/routers/tx/getCount.ts index 61d554c9e..f812a8c28 100644 --- a/packages/api/src/routers/tx/getCount.ts +++ b/packages/api/src/routers/tx/getCount.ts @@ -3,11 +3,11 @@ import { z } from "@blobscan/zod"; import type { Filters } from "../../middlewares/withFilters"; import { - hasCustomFilters, withAllFiltersSchema, withFilters, } from "../../middlewares/withFilters"; import { publicProcedure } from "../../procedures"; +import { buildStatsWhereClause, requiresDirectCount } from "../../utils/count"; const inputSchema = withAllFiltersSchema; @@ -15,20 +15,17 @@ const outputSchema = z.object({ totalTransactions: z.number(), }); +/** + * Counts transactions based on the provided filters. + * + * This function decides between counting transactions directly from the transaction table + * or using pre-calculated aggregated data from daily or overall transaction stats to + * improve performance. + * + * The choice depends on the specificity of the filters provided. + * + */ export async function countTxs(prisma: BlobscanPrismaClient, filters: Filters) { - if (!hasCustomFilters(filters)) { - const overallStats = await prisma.transactionOverallStats.findFirst({ - select: { - totalTransactions: true, - }, - where: { - AND: [{ category: null }, { rollup: null }], - }, - }); - - return overallStats?.totalTransactions ?? 0; - } - const { blockNumber, blockTimestamp, @@ -39,23 +36,47 @@ export async function countTxs(prisma: BlobscanPrismaClient, filters: Filters) { blockType, } = filters; - const blockFiltersExists = blockSlot || blockType; + if (requiresDirectCount(filters)) { + return prisma.transaction.count({ + where: { + blockNumber: blockNumber, + blockTimestamp: blockTimestamp, + category: transactionCategory, + rollup: transactionRollup, + OR: transactionAddresses, + block: { + slot: blockSlot, + transactionForks: blockType, + }, + }, + }); + } + + const where = buildStatsWhereClause(filters); - return prisma.transaction.count({ - where: { - blockNumber: blockNumber, - blockTimestamp: blockTimestamp, - category: transactionCategory, - rollup: transactionRollup, - OR: transactionAddresses, - block: blockFiltersExists - ? { - slot: blockSlot, - transactionForks: blockType, - } - : undefined, + // Get count by summing daily total transaction stats data if a date range is provided in filters + if (filters.blockTimestamp) { + const dailyStats = await prisma.transactionDailyStats.findMany({ + select: { + totalTransactions: true, + }, + where, + }); + + return dailyStats.reduce( + (acc, { totalTransactions }) => acc + totalTransactions, + 0 + ); + } + + const overallStats = await prisma.transactionOverallStats.findFirst({ + select: { + totalTransactions: true, }, + where, }); + + return overallStats?.totalTransactions ?? 0; } export const getCount = publicProcedure diff --git a/packages/api/src/utils/count.ts b/packages/api/src/utils/count.ts new file mode 100644 index 000000000..ded07ab8d --- /dev/null +++ b/packages/api/src/utils/count.ts @@ -0,0 +1,89 @@ +import { toDailyDatePeriod } from "@blobscan/dayjs"; +import { env } from "@blobscan/env"; +import { getRollupByAddress } from "@blobscan/rollups"; + +import type { Filters } from "../middlewares/withFilters"; + +function getRollupFromAddressFilter( + addressesFilter: Filters["transactionAddresses"] +) { + if (!addressesFilter) { + return; + } + + const fromAddress = addressesFilter.find(({ fromId }) => !!fromId)?.fromId; + + if (!fromAddress || typeof fromAddress !== "string") return; + + return getRollupByAddress(fromAddress, env.CHAIN_ID); +} + +/** + * Determines if a direct count operation must be performed or the value can be obtained by using + * pre-calculated values from aggregated stats tables, based on the provided filters. + * + * Aggregated stats are not available for certain filters, including: + * - Reorged blocks (`blockType` filter) + * - Specific `blockNumber` or `blockSlot` ranges + * - Filters involving multiple addresses or non-rollup addresses + * + * This function checks for those cases and returns `true` if a direct count is needed. + * + * @returns A boolean indicating if a direct count is required. + */ +export function requiresDirectCount({ + blockNumber, + blockSlot, + transactionAddresses, + blockType, +}: Filters) { + const blockNumberRangeFilterEnabled = !!blockNumber; + const reorgedFilterEnabled = !!blockType?.some; + const slotRangeFilterEnabled = !!blockSlot; + const addressFiltersCount = transactionAddresses?.length ?? 0; + const severalAddressesFilterEnabled = addressFiltersCount > 1; + const nonRollupAddressFilterEnabled = + addressFiltersCount === 1 && + !getRollupFromAddressFilter(transactionAddresses); + + return ( + blockNumberRangeFilterEnabled || + slotRangeFilterEnabled || + reorgedFilterEnabled || + severalAddressesFilterEnabled || + nonRollupAddressFilterEnabled + ); +} + +export function buildStatsWhereClause({ + blockTimestamp, + transactionCategory, + transactionRollup, + transactionAddresses, +}: Filters) { + const clauses = []; + // We set 'category' or 'rollup' to null when there are no corresponding filters + // because the db stores total statistics for each grouping in rows where + // 'category' or 'rollup' is null. + const rollup = + transactionRollup ?? + getRollupFromAddressFilter(transactionAddresses) ?? + null; + + const category = (rollup ? null : transactionCategory) ?? null; + + clauses.push({ category }, { rollup }); + + const { from, to } = toDailyDatePeriod({ + from: blockTimestamp?.gte, + to: blockTimestamp?.lt, + }); + + if (from || to) { + clauses.push({ day: { gte: from, lt: to } }); + } + + return { + AND: clauses, + }; +} diff --git a/packages/api/test/blob.test.ts b/packages/api/test/blob.test.ts index 7cce56f39..17b3ae462 100644 --- a/packages/api/test/blob.test.ts +++ b/packages/api/test/blob.test.ts @@ -1,18 +1,25 @@ import type { inferProcedureInput } from "@trpc/server"; import { beforeAll, beforeEach, describe, expect, it } from "vitest"; +import type { BlobDailyStats, BlobOverallStats, Prisma } from "@blobscan/db"; import { fixtures } from "@blobscan/test"; +import type { Category, Rollup } from "../enums"; import type { AppRouter } from "../src/app-router"; import { appRouter } from "../src/app-router"; import type { TRPCContext } from "../src/context"; import { createTestContext, + generateDailyCounts, runExpandsTestsSuite, runFiltersTestsSuite, runPaginationTestsSuite, } from "./helpers"; -import { getFilteredBlobs, runFilterTests } from "./test-suites/filters"; +import { + getFilteredBlobs, + requiresDirectCount, + runFilterTests, +} from "./test-suites/filters"; import { blobIdSchemaTestsSuite } from "./test-suites/schemas"; type GetByIdInput = inferProcedureInput; @@ -128,17 +135,111 @@ describe("Blob router", async () => { }); describe("getCount", () => { + // To test that the procedure retrieves the count from the stats table rather than + // performing a direct database count, we stored an arbitrary total blobs value in the stats table + // in each test, intentionally different from the actual number of blobs in the database. + // This setup allows us to verify that the procedure correctly uses the stats table count + // instead of performing a direct database count. + const STATS_TOTAL_BLOBS = 999999; + + async function createNewOverallStats( + overallStats: Partial + ) { + const data: Prisma.BlobOverallStatsCreateManyInput = { + category: null, + rollup: null, + updatedAt: new Date(), + totalBlobs: 0, + totalBlobSize: 0, + totalUniqueBlobs: 0, + ...overallStats, + }; + + await ctx.prisma.blobOverallStats.create({ + data, + }); + } + + async function createNewDailyStats(dailyStats: Partial) { + const data: Prisma.BlobDailyStatsCreateInput = { + day: new Date(), + category: null, + rollup: null, + totalBlobs: 0, + totalBlobSize: 0, + totalUniqueBlobs: 0, + ...dailyStats, + }; + + await ctx.prisma.blobDailyStats.create({ + data, + }); + } + it("should return the overall total blobs stat when no filters are provided", async () => { - await ctx.prisma.blobOverallStats.populate(); + const expectedTotalBlobs = STATS_TOTAL_BLOBS; + + await createNewOverallStats({ + category: null, + rollup: null, + totalBlobs: expectedTotalBlobs, + }); + const { totalBlobs } = await caller.blob.getCount({}); - expect(totalBlobs).toBe(fixtures.canonicalBlobs.length); + expect(totalBlobs).toBe(expectedTotalBlobs); }); runFilterTests(async (filters) => { + const categoryFilter: Category | null = + filters.rollup === "null" ? "ROLLUP" : null; + const rollupFilter: Rollup | null = + filters.rollup === "null" + ? null + : ((filters.rollup?.toUpperCase() ?? null) as Rollup | null); + const directCountRequired = requiresDirectCount(filters); + let expectedTotalBlobs = 0; + + if (directCountRequired) { + expectedTotalBlobs = getFilteredBlobs(filters).length; + } else { + const { startDate, endDate } = filters; + const dateFilterEnabled = startDate || endDate; + + if (dateFilterEnabled) { + const dailyCounts = generateDailyCounts( + { from: startDate, to: endDate }, + STATS_TOTAL_BLOBS + ); + expectedTotalBlobs = dailyCounts.reduce( + (acc, { count }) => acc + count, + 0 + ); + + await Promise.all( + dailyCounts.map(({ day, count }) => + createNewDailyStats({ + day, + category: categoryFilter, + rollup: rollupFilter, + totalBlobs: count, + }) + ) + ); + } else { + expectedTotalBlobs = STATS_TOTAL_BLOBS; + + await createNewOverallStats({ + category: categoryFilter, + rollup: rollupFilter, + totalBlobs: expectedTotalBlobs, + }); + } + } + const { totalBlobs } = await caller.blob.getCount(filters); - expect(totalBlobs).toBe(getFilteredBlobs(filters).length); + expect(totalBlobs).toBe(expectedTotalBlobs); }); }); }); diff --git a/packages/api/test/helpers.ts b/packages/api/test/helpers.ts index 67d857f27..26eec04ef 100644 --- a/packages/api/test/helpers.ts +++ b/packages/api/test/helpers.ts @@ -8,6 +8,8 @@ import { describe, expect, it } from "vitest"; import { createBlobPropagator } from "@blobscan/blob-propagator/src/blob-propagator"; import { createBlobStorageManager } from "@blobscan/blob-storage-manager"; +import type { DatePeriod } from "@blobscan/dayjs"; +import dayjs, { toDailyDate } from "@blobscan/dayjs"; import { prisma } from "@blobscan/db"; import type { Rollup } from "@blobscan/db"; import { env } from "@blobscan/env"; @@ -322,3 +324,28 @@ export async function unauthorizedRPCCallTest(rpcCall: () => Promise) { ); }); } + +export function generateDailyCounts({ from, to }: DatePeriod, count: number) { + const dailyCounts: { + day: Date; + count: number; + }[] = []; + const maxDays = 10; + const startDate = from + ? dayjs(from) + : toDailyDate(dayjs(to).subtract(maxDays)); + const endDate = to ? dayjs(to) : toDailyDate(dayjs(from).add(maxDays)); + + const days = endDate.diff(startDate, "days"); + + for (let i = 0; i < days; i++) { + const day = toDailyDate(startDate.add(i, "day")).toDate(); + + dailyCounts.push({ + day, + count, + }); + } + + return dailyCounts; +} diff --git a/packages/api/test/test-suites/filters.ts b/packages/api/test/test-suites/filters.ts index e6a1758dd..65d6c47f5 100644 --- a/packages/api/test/test-suites/filters.ts +++ b/packages/api/test/test-suites/filters.ts @@ -114,6 +114,28 @@ export function getFilteredBlobs(filters: FiltersSchema) { }); } +export function requiresDirectCount({ + endBlock, + endSlot, + from, + to, + type, + startBlock, + startSlot, +}: FiltersSchema) { + const blockNumberRangeFilterEnabled = !!startBlock || !!endBlock; + const reorgedFilterEnabled = type === "reorged"; + const slotRangeFilterEnabled = !!startSlot || !!endSlot; + const addressFilterEnabled = !!from || !!to; + + return ( + blockNumberRangeFilterEnabled || + reorgedFilterEnabled || + slotRangeFilterEnabled || + addressFilterEnabled + ); +} + export function runFilterTests( assertFilters: (filters: FiltersSchema) => Promise ) { diff --git a/packages/api/test/tx.test.ts b/packages/api/test/tx.test.ts index dbf0a0638..d02485a46 100644 --- a/packages/api/test/tx.test.ts +++ b/packages/api/test/tx.test.ts @@ -1,16 +1,27 @@ import { beforeAll, beforeEach, describe, expect, it } from "vitest"; +import type { + Prisma, + TransactionDailyStats, + TransactionOverallStats, +} from "@blobscan/db"; import { fixtures } from "@blobscan/test"; +import type { Category, Rollup } from "../enums"; import type { TRPCContext } from "../src"; import { appRouter } from "../src/app-router"; import { createTestContext, + generateDailyCounts, runExpandsTestsSuite, runFiltersTestsSuite, runPaginationTestsSuite, } from "./helpers"; -import { getFilteredTransactions, runFilterTests } from "./test-suites/filters"; +import { + getFilteredTransactions, + requiresDirectCount, + runFilterTests, +} from "./test-suites/filters"; describe("Transaction router", async () => { let caller: ReturnType; @@ -18,6 +29,7 @@ describe("Transaction router", async () => { beforeAll(async () => { ctx = await createTestContext(); + caller = appRouter.createCaller(ctx); }); @@ -52,6 +64,8 @@ describe("Transaction router", async () => { }); it("should get the total number of transactions for a rollup", async () => { + await ctx.prisma.transactionOverallStats.populate(); + const expectedTotalTransactions = await ctx.prisma.transaction.count({ where: { rollup: "BASE", @@ -94,18 +108,144 @@ describe("Transaction router", async () => { }); describe("getCount", () => { - it("should return the overall total transactions stat when no filters are provided", async () => { - await ctx.prisma.transactionOverallStats.populate(); + // To test that the procedure retrieves the count from the stats table rather than + // performing a direct database count, we stored an arbitrary total blobs value in the stats table + // in each test, intentionally different from the actual number of blobs in the database. + // This setup allows us to verify that the procedure correctly uses the stats table count + // instead of performing a direct database count. + + const STATS_TOTAL_TRANSACTIONS = 999999; + + async function createNewOverallStats( + overallStats: Partial + ) { + const data: Prisma.TransactionOverallStatsCreateManyInput = { + category: null, + rollup: null, + totalTransactions: 0, + avgBlobFee: 0, + avgBlobGasPrice: 0, + avgBlobMaxFee: 0, + avgMaxBlobGasFee: 0, + totalBlobAsCalldataFee: 0, + totalBlobAsCalldataGasUsed: 0, + totalBlobAsCalldataMaxFees: 0, + totalBlobFee: 0, + totalBlobMaxFees: 0, + totalBlobGasPrice: 0, + totalBlobGasUsed: 0, + totalBlobMaxGasFees: 0, + totalUniqueReceivers: 0, + totalUniqueSenders: 0, + avgBlobAsCalldataFee: 0, + avgBlobAsCalldataMaxFee: 0, + updatedAt: new Date(), + ...overallStats, + }; + + await ctx.prisma.transactionOverallStats.create({ + data, + }); + } + + async function createNewDailyStats( + dailyStats: Partial + ) { + const data: Prisma.TransactionDailyStatsCreateManyInput = { + day: new Date(), + category: null, + rollup: null, + totalTransactions: 0, + avgBlobFee: 0, + avgBlobGasPrice: 0, + avgBlobMaxFee: 0, + avgMaxBlobGasFee: 0, + totalBlobAsCalldataFee: 0, + totalBlobAsCalldataGasUsed: 0, + totalBlobAsCalldataMaxFees: 0, + totalBlobFee: 0, + totalBlobMaxFees: 0, + totalBlobGasUsed: 0, + totalUniqueReceivers: 0, + totalUniqueSenders: 0, + avgBlobAsCalldataFee: 0, + avgBlobAsCalldataMaxFee: 0, + ...dailyStats, + }; + + await ctx.prisma.transactionDailyStats.create({ + data, + }); + } + + it("should count txs correctly when no filters are provided", async () => { + const expectedTotalTransactions = STATS_TOTAL_TRANSACTIONS; + + await createNewOverallStats({ + category: null, + rollup: null, + totalTransactions: expectedTotalTransactions, + }); const { totalTransactions } = await caller.tx.getCount({}); - expect(totalTransactions).toBe(fixtures.canonicalTxs.length); + expect(totalTransactions).toBe(expectedTotalTransactions); }); runFilterTests(async (filters) => { + const categoryFilter: Category | null = + filters.rollup === "null" ? "ROLLUP" : null; + const rollupFilter: Rollup | null = + filters.rollup === "null" + ? null + : ((filters.rollup?.toUpperCase() ?? null) as Rollup | null); + const directCountRequired = requiresDirectCount(filters); + let expectedTotalTransactions = 0; + + if (directCountRequired) { + expectedTotalTransactions = getFilteredTransactions(filters).length; + } else { + const { startDate, endDate } = filters; + const dateFilterEnabled = startDate || endDate; + + if (dateFilterEnabled) { + const dailyCounts = generateDailyCounts( + { from: startDate, to: endDate }, + STATS_TOTAL_TRANSACTIONS + ); + expectedTotalTransactions = dailyCounts.reduce( + (acc, { count }) => acc + count, + 0 + ); + + await Promise.all( + dailyCounts.map(({ day, count }) => + createNewDailyStats({ + day, + totalTransactions: count, + category: categoryFilter, + rollup: rollupFilter, + }) + ) + ); + } else { + expectedTotalTransactions = STATS_TOTAL_TRANSACTIONS; + + await createNewOverallStats({ + totalTransactions: expectedTotalTransactions, + category: categoryFilter, + rollup: rollupFilter, + }); + } + } + const { totalTransactions } = await caller.tx.getCount(filters); - expect(totalTransactions).toBe(getFilteredTransactions(filters).length); + const expectMsg = directCountRequired + ? "Expect count to match direct count" + : "Expect count to match precomputed aggregated stats value"; + + expect(totalTransactions, expectMsg).toBe(expectedTotalTransactions); }); }); }); diff --git a/packages/dayjs/src/date.ts b/packages/dayjs/src/date.ts index d757a9ff3..6fd163ae2 100644 --- a/packages/dayjs/src/date.ts +++ b/packages/dayjs/src/date.ts @@ -51,17 +51,22 @@ export function toDailyDate( return date_[startOfOrEndOfDay]("day"); } -export function toDailyDatePeriod( - datePeriodLike?: DatePeriodLike -): Required { - const { from, to } = datePeriodLike || {}; +export function toDailyDatePeriod(datePeriodLike: DatePeriodLike): DatePeriod { + const { from, to } = datePeriodLike; if (from && to && dayjs(to).isBefore(from)) { throw new Error(`Invalid date period. Start date is after end date`); } - return { - from: from ? toDailyDate(from, "startOf").toDate() : MIN_DATE, - to: to ? toDailyDate(to, "endOf").toDate() : new Date(), - }; + const datePeriod: DatePeriod = {}; + + if (from) { + datePeriod.from = toDailyDate(from, "startOf").toDate(); + } + + if (to) { + datePeriod.to = toDailyDate(to, "endOf").toDate(); + } + + return datePeriod; } diff --git a/packages/db/prisma/extensions/stats.ts b/packages/db/prisma/extensions/stats.ts index 40fdf70b1..972ca6ed1 100644 --- a/packages/db/prisma/extensions/stats.ts +++ b/packages/db/prisma/extensions/stats.ts @@ -8,7 +8,7 @@ import { aggregateTxOverallStats, } from "@prisma/client/sql"; -import { toDailyDatePeriod } from "@blobscan/dayjs"; +import { MIN_DATE, toDailyDatePeriod } from "@blobscan/dayjs"; import { curryPrismaExtensionFnSpan } from "../instrumentation"; import type { BlockNumberRange, DatePeriodLike } from "../types"; @@ -32,7 +32,9 @@ export const statsExtension = Prisma.defineExtension((prisma) => }); }, populate(dailyDatePeriod?: DatePeriodLike) { - const { from, to } = toDailyDatePeriod(dailyDatePeriod); + const { from: from = MIN_DATE, to: to = new Date() } = dailyDatePeriod + ? toDailyDatePeriod(dailyDatePeriod) + : {}; return prisma.$queryRawTyped(aggregateBlobDailyStats(from, to)); }, @@ -56,7 +58,9 @@ export const statsExtension = Prisma.defineExtension((prisma) => }); }, populate(dailyDatePeriod?: DatePeriodLike) { - const { from, to } = toDailyDatePeriod(dailyDatePeriod); + const { from: from = MIN_DATE, to: to = new Date() } = dailyDatePeriod + ? toDailyDatePeriod(dailyDatePeriod) + : {}; return prisma.$queryRawTyped(aggregateBlockDailyStats(from, to)); }, @@ -82,7 +86,9 @@ export const statsExtension = Prisma.defineExtension((prisma) => }); }, async populate(dailyDatePeriod?: DatePeriodLike) { - const { from, to } = toDailyDatePeriod(dailyDatePeriod); + const { from: from = MIN_DATE, to: to = new Date() } = dailyDatePeriod + ? toDailyDatePeriod(dailyDatePeriod) + : {}; return prisma.$queryRawTyped(aggregateTxDailyStats(from, to)); }, diff --git a/packages/db/test/helpers/suites/daily-stats.ts b/packages/db/test/helpers/suites/daily-stats.ts index cb0cfea0d..693667025 100644 --- a/packages/db/test/helpers/suites/daily-stats.ts +++ b/packages/db/test/helpers/suites/daily-stats.ts @@ -46,7 +46,8 @@ function getExpectedAggregatedDays( model: DailyStatsModel, datePeriodLike: DatePeriodLike ) { - const { from, to } = toDailyDatePeriod(datePeriodLike); + const { from: from = MIN_DATE, to: to = new Date() } = + toDailyDatePeriod(datePeriodLike); let elementTimestamps: string[]; switch (model) { diff --git a/packages/test/src/fixtures/index.ts b/packages/test/src/fixtures/index.ts index 412e3faac..27dbde873 100644 --- a/packages/test/src/fixtures/index.ts +++ b/packages/test/src/fixtures/index.ts @@ -8,7 +8,7 @@ import type { } from "@prisma/client"; import type { DatePeriodLike } from "@blobscan/dayjs"; -import dayjs, { toDailyDatePeriod } from "@blobscan/dayjs"; +import dayjs, { MIN_DATE, toDailyDatePeriod } from "@blobscan/dayjs"; import POSTGRES_DATA from "./postgres/data.json"; @@ -165,15 +165,14 @@ export const fixtures = { getBlocks({ blockNumberRange, datePeriod }: GetOptions) { if (!datePeriod && !blockNumberRange) return fixtures.canonicalBlocks; - const dailyDatePeriod = toDailyDatePeriod(datePeriod); + const { from = MIN_DATE, to = new Date() } = datePeriod + ? toDailyDatePeriod(datePeriod) + : {}; const fromBlock = blockNumberRange?.from ?? 0; const toBlock = blockNumberRange?.to ?? Number.MAX_SAFE_INTEGER; return fixtures.canonicalBlocks.filter((b) => { - const isInDateRange = dayjs(b.timestamp).isBetween( - dailyDatePeriod.from, - dailyDatePeriod.to - ); + const isInDateRange = dayjs(b.timestamp).isBetween(from, to); const isInBlockNumberRange = b.number >= fromBlock && b.number <= toBlock; return isInDateRange && isInBlockNumberRange; @@ -266,6 +265,7 @@ export const fixtures = { prisma.transaction.deleteMany(), prisma.addressCategoryInfo.deleteMany(), prisma.address.deleteMany(), + prisma.addressCategoryInfo.deleteMany(), prisma.block.deleteMany(), prisma.blockDailyStats.deleteMany(), prisma.transactionDailyStats.deleteMany(),