Skip to content

Commit

Permalink
Merge pull request #370 from Blobscan/feat/web/starknet-blobs-decoding
Browse files Browse the repository at this point in the history
feat: decode Starknet blobs
  • Loading branch information
PJColombo committed May 12, 2024
2 parents 1abc00a + cfb5178 commit ca70779
Show file tree
Hide file tree
Showing 52 changed files with 1,339 additions and 172 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-lions-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@blobscan/web": minor
---

Added starknet decoded blob view
5 changes: 5 additions & 0 deletions .changeset/chilly-sloths-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@blobscan/web": patch
---

Fixed dropdown list
6 changes: 6 additions & 0 deletions .changeset/mighty-trainers-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@blobscan/blob-decoder": patch
"@blobscan/web": patch
---

Handled blob decoding errors
5 changes: 5 additions & 0 deletions .changeset/real-avocados-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@blobscan/web": minor
---

Added rollup tag to blob details page
5 changes: 5 additions & 0 deletions .changeset/swift-plants-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@blobscan/blob-decoder": minor
---

Added starknet blob decoder
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ To learn more about Blobscan, please check out our [documentation website](https

# Features

- **Blob explorer** - Delve into blobs and examine their content. Decode them in various formats.
- **Search Capabilities** - Look up blobs by their versioned hash, kzg commitment, transaction hash, slot or block number, along with associated transactions and blocks.
- **Blob explorer** - Delve into blobs and examine their content.
- **Search capabilities** - Look up blobs by their versioned hash, kzg commitment, transaction hash, slot or block number, along with associated transactions and blocks.
- **Blob persistence** - For consistent availability even after pruning from the chain, blobscan support storing blobs in multiple storage systems, both centralized and decentralized.
- **Blob decoding** - Seamlessly access detailed insights into blobs encoded in specialized formats, including rollup blobs or blobscriptions.
- **Rich analytics dashboard** - Gain insights into blobs, blocks and transactions. View diverse charts and metrics.
- **Blob API** - Blobscan's API facilitates queries on blobs, their associated blocks and transactions, along with relevant statistics and metrics.
- **Open source** - Blobscan is open-source and available to everyone. We welcome contributions too. Check out our issues to see how you can help.
Expand Down
48 changes: 48 additions & 0 deletions apps/web/banner.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
function maskSensitiveData(sensitiveData) {
return sensitiveData?.replace(/./g, "*");
}

export function printBanner() {
console.log(" ____ _ _");
console.log("| __ )| | ___ | |__ ___ ___ __ _ _ __");
console.log("| _ \\| |/ _ \\| '_ \\/ __|/ __/ _` | '_ \\");
console.log("| |_) | | (_) | |_) \\__ \\ (_| (_| | | | |");
console.log("|____/|_|\\___/|_.__/|___/\\___\\__,_|_| |_|");
console.log("Blobscan Web App (EIP-4844 blob explorer) - blobscan.com");
console.log("=======================================================\n");
console.log(
`Configuration: network=${process.env.NEXT_PUBLIC_NETWORK_NAME} explorer=${
process.env.NEXT_PUBLIC_EXPLORER_BASE_URL
} beaconExplorer=${
process.env.NEXT_PUBLIC_BEACON_BASE_URL
} feedbackEnabled=${!!process.env
.FEEDBACK_WEBHOOK_URL} tracesEnabled=${!!process.env
.TRACES_ENABLED} sentryEnabled=${!!process.env
.NEXT_PUBLIC_SENTRY_DSN_WEB}`
);
console.log(
`Blob storage manager configuration: chainId=${process.env.CHAIN_ID}, file_system=${process.env.FILE_SYSTEM_STORAGE_ENABLED} postgres=${process.env.POSTGRES_STORAGE_ENABLED}, gcs=${process.env.GOOGLE_STORAGE_ENABLED}, swarm=${process.env.SWARM_STORAGE_ENABLED}`
);

if (process.env.GOOGLE_STORAGE_ENABLED) {
console.log(
`GCS configuration: bucketName=${
process.env.GOOGLE_STORAGE_BUCKET_NAME
}, projectId=${maskSensitiveData(
process.env.GOOGLE_STORAGE_PROJECT_ID
)}, apiEndpoint=${process.env.GOOGLE_STORAGE_API_ENDPOINT}`
);
}

if (process.env.SWARM_STORAGE_ENABLED) {
console.log(
`Swarm configuration: beeEndpoint=${process.env.BEE_ENDPOINT}, debugEndpoint=${process.env.BEE_DEBUG_ENDPOINT}`
);
}

if (process.env.FILE_SYSTEM_STORAGE_ENABLED) {
console.log(
`File system configuration: blobDirPath=${process.env.FILE_SYSTEM_STORAGE_PATH}`
);
}
}
8 changes: 8 additions & 0 deletions apps/web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { withSentryConfig } from "@sentry/nextjs";

// Importing env files here to validate on build
import "./src/env.mjs";
import { printBanner } from "./banner.mjs";

printBanner();

const bundleAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === "true",
Expand Down Expand Up @@ -66,6 +69,11 @@ const config = {
// Modify the file loader rule to ignore *.svg, since we have it handled now.
fileLoaderRule.exclude = /\.svg$/i;

config.experiments = {
asyncWebAssembly: true,
layers: true,
};

return config;
},

Expand Down
6 changes: 3 additions & 3 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
"analyze": "ANALYZE=true pnpm with-env next build",
"build": "pnpm with-env next build",
"clean": "git clean -xdf .next .turbo node_modules",
"print-banner": "pnpm with-env ts-node ./src/scripts/print-banner.ts",
"dev": "pnpm print-banner && pnpm with-env next dev -p 3000",
"dev": "pnpm with-env next dev -p 3000",
"lint": "dotenv -v SKIP_ENV_VALIDATION=1 next lint",
"lint:fix": "pnpm lint --fix",
"start": "pnpm print-banner && pnpm with-env next start",
"start": "pnpm with-env next start",
"svg:format": "svgo -r ./src/icons ./public",
"type-check": "tsc --noEmit",
"with-env": "dotenv -e ../../.env -v BLOB_PROPAGATOR_ENABLED=false --"
},
"dependencies": {
"@blobscan/api": "workspace:^0.9.0",
"@blobscan/blob-decoder": "workspace:^0.0.1",
"@blobscan/dayjs": "workspace:^0.0.2",
"@blobscan/open-telemetry": "workspace:^0.0.7",
"@fontsource/inter": "^4.5.15",
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/blob-decoder.worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { decodeBlob, isValidDecoder } from "@blobscan/blob-decoder";

type BlobDecoderEvent = {
blobData?: string;
decoder?: string;
};

addEventListener("message", (event: MessageEvent<BlobDecoderEvent>) => {
try {
const { decoder, blobData } = event.data;

if (!decoder) {
throw new Error("No decoder provided");
}

if (!blobData) {
throw new Error("No blob data provided");
}

if (isValidDecoder(decoder)) {
decodeBlob(blobData, decoder).then((result) => {
postMessage({ decodedBlob: result });
});
} else {
throw new Error(`${decoder} decoder is not supported`);
}
} catch (err) {
postMessage({ error: (err as Error).message });
}
});
15 changes: 15 additions & 0 deletions apps/web/src/components/BlobViewer/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { FC } from "react";
import { ExclamationCircleIcon } from "@heroicons/react/24/outline";

export type ErrorMessageProps = {
error: string | Error;
};

export const ErrorMessage: FC<ErrorMessageProps> = function ({ error }) {
return (
<div className="flex h-32 w-full items-center justify-center gap-2 text-sm text-error-600 dark:text-error-300">
<ExclamationCircleIcon className="h-6 w-6" />
<div>{error instanceof Error ? error.message : error}</div>
</div>
);
};
20 changes: 20 additions & 0 deletions apps/web/src/components/BlobViewer/Views/RawBlobView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { FC } from "react";
import Skeleton from "react-loading-skeleton";

import "react-loading-skeleton/dist/skeleton.css";
import { ExpandableContent } from "../../ExpandableContent";
import type { BlobViewProps } from "../index";

export type RawBlobViewProps = BlobViewProps<string>;

export const RawBlobView: FC<RawBlobViewProps> = function ({ data }) {
return (
<div className="break-words p-3 text-left text-sm leading-7">
{data !== undefined ? (
<ExpandableContent>{data}</ExpandableContent>
) : (
<Skeleton count={10} />
)}
</div>
);
};
163 changes: 163 additions & 0 deletions apps/web/src/components/BlobViewer/Views/StarknetBlobView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { useState, useEffect, useRef } from "react";
import type { FC } from "react";

import type { DecodedStarknetBlob } from "@blobscan/blob-decoder";

import { Table } from "~/components/Table";
import { Toggle } from "~/components/Toggle";
import { ErrorMessage } from "../ErrorMessage";
import type { BlobViewProps } from "../index";

export type StarknetBlobViewProps = BlobViewProps<DecodedStarknetBlob>;

function hexToBigInt(hex: string): string {
return BigInt(hex).toString(10);
}
export const StarknetBlobView: FC<StarknetBlobViewProps> = function ({
data: stateDiffsProps,
}) {
const originalStateDiffsRef = useRef<DecodedStarknetBlob>();
const [stateDiffs, setStateDiffs] = useState<
DecodedStarknetBlob | undefined | null
>(stateDiffsProps);
const [originalDataToggle, setOriginalDataToggle] = useState<boolean>(false);

useEffect(() => {
if (originalDataToggle) {
let cachedOriginalStateDiffs = originalStateDiffsRef.current;
if (!cachedOriginalStateDiffs) {
cachedOriginalStateDiffs = stateDiffsProps?.map(
({
contractAddress,
newClassHash,
nonce,
numberOfStorageUpdates,
storageUpdates,
}) => ({
contractAddress: hexToBigInt(contractAddress),
newClassHash: hexToBigInt(newClassHash),
nonce,
numberOfStorageUpdates,
storageUpdates: storageUpdates.map(({ key, value }) => ({
key: hexToBigInt(key),
value: hexToBigInt(value),
})),
})
);

originalStateDiffsRef.current = cachedOriginalStateDiffs;
}

setStateDiffs(cachedOriginalStateDiffs);
} else {
setStateDiffs(stateDiffsProps);
}
}, [originalDataToggle, stateDiffsProps, originalStateDiffsRef]);

if (!stateDiffs) {
return <ErrorMessage error="No starknet state diffs found" />;
}

return (
<Table
expandableRowsMode
headers={[
{
cells: [
{
item: (
<div className="relative w-full">
Starknet State Diffs ({stateDiffs.length}){" "}
<div className="absolute right-10 top-1">
<div className="flex items-center justify-center gap-2">
<Toggle
onToggle={() =>
setOriginalDataToggle((prevToggle) => !prevToggle)
}
/>{" "}
<div className="text-xs text-contentSecondary-light dark:text-contentSecondary-dark">
Original Data
</div>
</div>
</div>
</div>
),
alignment: "center",
},
],
spanFullRow: true,
},
{
cells: [
{
item: "Contract Address",
},
{
item: "New Class Hash",
},
{
item: "Nonce",
},
],
},
]}
rows={stateDiffs.map(
({ contractAddress, newClassHash, nonce, storageUpdates }) => ({
cells: [
{
item: contractAddress,
},
{
item: newClassHash,
},
{
item: nonce,
},
],
expandItem: (
<Table
className="max-h-[420px]"
size="xs"
alignment="center"
variant="simple"
headers={[
{
cells: [
{
item: `Storage Updades (${storageUpdates.length})`,
spanFullRow: true,
},
],
className: "border-none",
},
{
cells: [
{
item: "Key",
},
{
item: "Value",
},
],
className: "dark:border-border-dark/20",
sticky: true,
},
]}
rows={storageUpdates.map(({ key, value }) => ({
cells: [
{
item: key,
},
{
item: value,
},
],
className: "dark:border-border-dark/10",
}))}
/>
),
})
)}
/>
);
};
2 changes: 2 additions & 0 deletions apps/web/src/components/BlobViewer/Views/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./RawBlobView";
export * from "./StarknetBlobView";

0 comments on commit ca70779

Please sign in to comment.