Skip to content

Commit

Permalink
Fix display decoded data for Optimism blobs (#608)
Browse files Browse the repository at this point in the history
* fix timestamp

* chore: Add a trailing '...' to the parent L2 block hash

* chore: add full block hash

* fix(web): format block timestamp in tx page

* Update packages/api/src/routers/block/getByPartialHash.ts

Co-authored-by: elessar.eth <[email protected]>

* chore: move getFullBlockHash to utils

* refactor(api): adds parseDecodedFields

* refactor(web): remove unused file from tsconfig.json

* chore: update snapshots

* test: update snapshots

* refactor(api): add logging for failed block hash retrieval in parseOptimismDecodedData

* Update packages/api/src/blob-parse/optimism.ts

* refactor: rename to autocompleteBlockHash

* refactor(api): update autocompleteBlockHash to handle multiple results and add logging

* chore: adds changeset

* chore: update changeset

* Update packages/api/src/utils/autocompleteBlockHash.ts

---------

Co-authored-by: elessar.eth <[email protected]>
Co-authored-by: Pablo Castellano <[email protected]>
  • Loading branch information
3 people authored Nov 12, 2024
1 parent 5272928 commit 4226258
Show file tree
Hide file tree
Showing 8 changed files with 372 additions and 91 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-eagles-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@blobscan/web": patch
---

Improved decoding display for Optimism blobs; removed truncated Parent L2 block hash link, fixed L1 origin block redirection, and corrected timestamp.
30 changes: 12 additions & 18 deletions apps/web/src/pages/tx/[hash].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useMemo } from "react";
import type { NextPage } from "next";
import { useRouter } from "next/router";

import { parseDecodedData } from "~/utils/decoded-transaction";
import { RollupBadge } from "~/components/Badges/RollupBadge";
import { Card } from "~/components/Cards/Card";
import { BlobCard } from "~/components/Cards/SurfaceCards/BlobCard";
Expand Down Expand Up @@ -230,9 +229,9 @@ const Tx: NextPage = () => {
}

const decodedData =
rawTxData && rawTxData.decodedFields
? parseDecodedData(rawTxData.decodedFields)
: null;
rawTxData?.decodedFields?.type === "optimism"
? rawTxData.decodedFields.payload
: undefined;

return (
<>
Expand Down Expand Up @@ -265,7 +264,14 @@ const Tx: NextPage = () => {
name: "Timestamp since L2 genesis",
value: (
<div className="whitespace-break-spaces">
{formatTimestamp(decodedData.timestampSinceL2Genesis)}
{tx
? formatTimestamp(
tx.blockTimestamp.subtract(
decodedData.timestampSinceL2Genesis,
"ms"
)
)
: ""}
</div>
),
},
Expand All @@ -277,19 +283,7 @@ const Tx: NextPage = () => {
name: "Parent L2 block hash",
value: (
<div className="flex items-center gap-2">
<Link
href={
"https://etherscan.io/block/" +
"0x" +
decodedData.parentL2BlockHash
}
>
{"0x" + decodedData.parentL2BlockHash}
</Link>
<CopyToClipboard
value={"0x" + decodedData.parentL2BlockHash}
label="Copy parent L2 block hash"
/>
{"0x" + decodedData.parentL2BlockHash + "..."}
</div>
),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { z } from "zod";

import { logger } from "@blobscan/logger";

import { autocompleteBlockHash } from "../utils/autocompleteBlockHash";

export const OptimismDecodedDataSchema = z.object({
timestampSinceL2Genesis: z.number(),
lastL1OriginNumber: z.number(),
Expand All @@ -13,7 +17,9 @@ export const OptimismDecodedDataSchema = z.object({

type OptimismDecodedData = z.infer<typeof OptimismDecodedDataSchema>;

export function parseDecodedData(data: string): OptimismDecodedData | null {
export async function parseOptimismDecodedData(
data: string
): Promise<OptimismDecodedData | null> {
let json;

try {
Expand All @@ -28,5 +34,15 @@ export function parseDecodedData(data: string): OptimismDecodedData | null {
return null;
}

const hash = await autocompleteBlockHash(decoded.data.l1OriginBlockHash);

if (hash) {
decoded.data.l1OriginBlockHash = hash;
} else {
logger.error(
`Failed to get full block hash for L1 origin block hash: ${decoded.data.l1OriginBlockHash}`
);
}

return decoded.data;
}
36 changes: 36 additions & 0 deletions packages/api/src/blob-parse/parse-decoded-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { z } from "zod";

import {
parseOptimismDecodedData,
OptimismDecodedDataSchema,
} from "./optimism";

const OptimismSchema = z.object({
type: z.literal("optimism"),
payload: OptimismDecodedDataSchema,
});

const UnknownSchema = z.object({
type: z.literal("unknown"),
payload: z.string(),
});

export const decodedFields = z.union([OptimismSchema, UnknownSchema]);

type DecodedFields = z.infer<typeof decodedFields>;

export async function parseDecodedFields(data: string): Promise<DecodedFields> {
const optimismDecodedData = await parseOptimismDecodedData(data);

if (optimismDecodedData) {
return {
type: "optimism",
payload: optimismDecodedData,
};
}

return {
type: "unknown",
payload: data,
};
}
17 changes: 12 additions & 5 deletions packages/api/src/routers/tx/common/serializers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { z } from "@blobscan/zod";

import {
decodedFields,
parseDecodedFields,
} from "../../../blob-parse/parse-decoded-fields";
import {
serializeExpandedBlobData,
serializeExpandedBlock,
Expand Down Expand Up @@ -42,7 +46,7 @@ const baseSerializedTransactionFieldsSchema = z.object({
.merge(serializedExpandedBlobDataSchema)
),
block: serializedExpandedBlockSchema.optional(),
decodedFields: z.string().optional(),
decodedFields: decodedFields.optional(),
});

export const serializedTransactionSchema =
Expand Down Expand Up @@ -121,15 +125,18 @@ export function serializeBaseTransactionFields(
};
}

export function serializeTransaction(
export async function serializeTransaction(
txQuery: FullQueriedTransaction
): SerializedTransaction {
const serializedBaseTx = serializeBaseTransactionFields(txQuery);
): Promise<SerializedTransaction> {
const serializedBaseTx = await serializeBaseTransactionFields(txQuery);
const serializedAdditionalTx = serializeDerivedTxBlobGasFields(txQuery);

const decodedFieldsString = JSON.stringify(txQuery.decodedFields);
const decodedFields = await parseDecodedFields(decodedFieldsString);

return {
...serializedBaseTx,
...serializedAdditionalTx,
decodedFields: JSON.stringify(txQuery.decodedFields),
decodedFields,
};
}
8 changes: 5 additions & 3 deletions packages/api/src/routers/tx/getAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ export const getAll = publicProcedure
countOp,
]);

const transactions = await Promise.all(
queriedTxs.map(addDerivedFieldsToTransaction).map(serializeTransaction)
);

return {
transactions: queriedTxs
.map(addDerivedFieldsToTransaction)
.map(serializeTransaction),
transactions,
...(count ? { totalTransactions: txCountOrStats } : {}),
};
});
29 changes: 29 additions & 0 deletions packages/api/src/utils/autocompleteBlockHash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { prisma } from "@blobscan/db";
import { logger } from "@blobscan/logger";

/* Autocomplete a block hash from a truncated version of it.
@param partialHash - The first bytes of a block hash.
@returns The block hash, if there is a single ocurrence, or null.
*/
export async function autocompleteBlockHash(partialHash: string) {
const blocks = await prisma.block.findMany({
where: {
hash: {
startsWith: partialHash,
},
},
select: {
hash: true,
},
});

if (blocks[0] === undefined) {
return null;
}

if (blocks.length > 1) {
logger.error(`Multiple blocks found for hash ${partialHash}`);
}

return blocks[0].hash;
}
Loading

0 comments on commit 4226258

Please sign in to comment.