-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api): add support for fetching blocks by slot (#700)
* feat(api): add support for fetching blocks by slot * chore: add changeset * fix(api): extract logic into a separate procedure * test(api): add test to ensure getBySlot does not return reorg blocks
- Loading branch information
Showing
9 changed files
with
324 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@blobscan/api": minor | ||
--- | ||
|
||
Added support for fetching blocks by slot |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from "./queries"; | ||
export * from "./selects"; | ||
export * from "./serializers"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import type { BlobStorageManager } from "@blobscan/blob-storage-manager"; | ||
import type { BlobscanPrismaClient, Prisma } from "@blobscan/db"; | ||
|
||
import type { Expands } from "../../../middlewares/withExpands"; | ||
import type { Filters } from "../../../middlewares/withFilters"; | ||
import { | ||
calculateDerivedTxBlobGasFields, | ||
retrieveBlobData, | ||
} from "../../../utils"; | ||
import { createBlockSelect } from "./selects"; | ||
import type { QueriedBlock } from "./serializers"; | ||
|
||
export type BlockId = "hash" | "number" | "slot"; | ||
export type BlockIdField = | ||
| { type: "hash"; value: string } | ||
| { type: "number"; value: number } | ||
| { type: "slot"; value: number }; | ||
|
||
function buildBlockWhereClause( | ||
{ type, value }: BlockIdField, | ||
filters: Filters | ||
): Prisma.BlockWhereInput { | ||
switch (type) { | ||
case "hash": { | ||
return { hash: value }; | ||
} | ||
case "number": { | ||
return { number: value, transactionForks: filters.blockType }; | ||
} | ||
case "slot": { | ||
return { slot: value, transactionForks: filters.blockType }; | ||
} | ||
} | ||
} | ||
|
||
export async function fetchBlock( | ||
blockId: BlockIdField, | ||
{ | ||
blobStorageManager, | ||
prisma, | ||
filters, | ||
expands, | ||
}: { | ||
blobStorageManager: BlobStorageManager; | ||
prisma: BlobscanPrismaClient; | ||
filters: Filters; | ||
expands: Expands; | ||
} | ||
) { | ||
const where = buildBlockWhereClause(blockId, filters); | ||
|
||
const queriedBlock = await prisma.block.findFirst({ | ||
select: createBlockSelect(expands), | ||
where, | ||
}); | ||
|
||
if (!queriedBlock) { | ||
return; | ||
} | ||
|
||
const block: QueriedBlock = queriedBlock; | ||
|
||
if (expands.transaction) { | ||
block.transactions = block.transactions.map((tx) => { | ||
const { blobAsCalldataGasUsed, blobGasUsed, gasPrice, maxFeePerBlobGas } = | ||
tx; | ||
const derivedFields = | ||
maxFeePerBlobGas && blobAsCalldataGasUsed && blobGasUsed && gasPrice | ||
? calculateDerivedTxBlobGasFields({ | ||
blobAsCalldataGasUsed, | ||
blobGasUsed, | ||
gasPrice, | ||
blobGasPrice: block.blobGasPrice, | ||
maxFeePerBlobGas, | ||
}) | ||
: {}; | ||
|
||
return { | ||
...tx, | ||
...derivedFields, | ||
}; | ||
}); | ||
} | ||
|
||
if (expands.blobData) { | ||
const txsBlobs = block.transactions.flatMap((tx) => tx.blobs); | ||
|
||
await Promise.all( | ||
txsBlobs.map(async ({ blob }) => { | ||
if (blob.dataStorageReferences?.length) { | ||
const data = await retrieveBlobData(blobStorageManager, blob); | ||
|
||
blob.data = data; | ||
} | ||
}) | ||
); | ||
} | ||
|
||
return block; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { TRPCError } from "@trpc/server"; | ||
|
||
import { z } from "@blobscan/zod"; | ||
|
||
import { | ||
createExpandsSchema, | ||
withExpands, | ||
} from "../../middlewares/withExpands"; | ||
import { | ||
withFilters, | ||
withSortFilterSchema, | ||
} from "../../middlewares/withFilters"; | ||
import { publicProcedure } from "../../procedures"; | ||
import type { BlockIdField } from "./common"; | ||
import { fetchBlock, serializeBlock } from "./common"; | ||
|
||
const inputSchema = z | ||
.object({ | ||
slot: z.coerce.number().int().positive(), | ||
}) | ||
.merge(withSortFilterSchema) | ||
.merge(createExpandsSchema(["transaction", "blob", "blob_data"])); | ||
|
||
export const getBySlot = publicProcedure | ||
.meta({ | ||
openapi: { | ||
method: "GET", | ||
path: `/slots/{slot}`, | ||
tags: ["slots"], | ||
summary: "retrieves block details for given slot.", | ||
}, | ||
}) | ||
.input(inputSchema) | ||
.use(withExpands) | ||
.use(withFilters) | ||
.query( | ||
async ({ | ||
ctx: { blobStorageManager, prisma, filters, expands }, | ||
input: { slot }, | ||
}) => { | ||
const blockIdField: BlockIdField = { type: "slot", value: slot }; | ||
|
||
const block = await fetchBlock(blockIdField, { | ||
blobStorageManager, | ||
prisma, | ||
filters, | ||
expands, | ||
}); | ||
|
||
if (!block) { | ||
throw new TRPCError({ | ||
code: "NOT_FOUND", | ||
message: `Block with slot ${slot} not found`, | ||
}); | ||
} | ||
|
||
return serializeBlock(block); | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,14 @@ | ||
import { t } from "../../trpc-client"; | ||
import { getAll } from "./getAll"; | ||
import { getByBlockId } from "./getByBlockId"; | ||
import { getBySlot } from "./getBySlot"; | ||
import { getCount } from "./getCount"; | ||
import { getLatestBlock } from "./getGasPrice"; | ||
|
||
export const blockRouter = t.router({ | ||
getAll, | ||
getByBlockId, | ||
getBySlot, | ||
getCount, | ||
getLatestBlock, | ||
}); |
Oops, something went wrong.