Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support wasm msgs #24

Merged
merged 11 commits into from
Nov 18, 2024
1 change: 1 addition & 0 deletions components/mermaid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mermaid.initialize({
lineColor: "#fff",
actorTextColor: "#fff",
actorBorder: "#fff",
activationBkgColor: "#fff",
},
themeCSS: `
.actor {
Expand Down
19 changes: 15 additions & 4 deletions components/tx-data/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import { useTx } from "@/hooks/api";
import { sequenceDiagramFromSpans } from "@/lib/mermaid";
import { Loader2 } from "lucide-react";
import { useState } from "react";
import Mermaid from "../mermaid";
import { Badge } from "../ui/badge";
import SpanDetails from "./span-details";
import SpansDetails from "./spans-details";

type TxDataProps = {
txId: string;
Expand All @@ -21,6 +23,7 @@ export default function TxData({ txId, spanId }: TxDataProps) {
if (!tx) return "Couldn't find a Tx with id: " + txId;

const mermaidChart = sequenceDiagramFromSpans(tx.spans);
const canRenderMermaid = mermaidChart !== "sequenceDiagram";
const span = tx.spans.find((span) => span.spanId === spanIdToFind);

return (
Expand All @@ -30,10 +33,18 @@ export default function TxData({ txId, spanId }: TxDataProps) {
Transaction {txId}
</Badge>
</a>
{!isFetching ? (
<Mermaid chart={mermaidChart} setSpanId={setSpanIdToFind} />
) : null}
{span ? <SpanDetails span={span} /> : null}
{canRenderMermaid ? (
<>
{isFetching ? (
<Loader2 className="animate-spin mx-auto" />
) : (
<Mermaid chart={mermaidChart} setSpanId={setSpanIdToFind} />
)}
{span ? <SpanDetails span={span} /> : null}
</>
) : (
<SpansDetails spans={tx.spans} />
)}
</div>
);
}
20 changes: 20 additions & 0 deletions components/tx-data/spans-details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Span } from "@/types/txs";
import SpanDetails from "./span-details";

type SpansDetailsProps = {
spans: readonly Span[];
};

export default function SpansDetails({ spans }: SpansDetailsProps) {
return spans.map((span) => (
<>
{spans.length > 1 ? (
<h2>
Operation <span className="font-bold">{span.operationName}</span>{" "}
<span className="font-mono">{span.spanId}</span>
</h2>
) : null}
<SpanDetails key={span.spanId} span={span} />
</>
));
}
42 changes: 23 additions & 19 deletions components/txs-table/txs-table-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,30 @@ import { DataTableKvFilter } from "../data-table/data-table-kv-filter";
import { DataTableViewOptions } from "../data-table/data-table-view-options";

const getSelectedValue = <TData,>(table: Table<TData>) => {
if (
!table.getColumn("operationName")?.getFilterValue() &&
!table.getColumn("tags")?.getFilterValue()
) {
return "empty";
}

if ((table.getColumn("operationName")?.getFilterValue() as string) ?? "") {
return "custom";
}

const operationName = table.getColumn("operationName")?.getFilterValue() as
| string
| undefined;
const tags = table.getColumn("tags")?.getFilterValue() as
| Map<string, string>
| undefined;

if (tags?.size === 1 && tags?.get("raw_tx") === "*") {
if (!operationName && !tags) {
return "empty";
}

if (
operationName === "query" &&
tags?.size === 1 &&
tags?.get("request") === "*Bank(Send*"
) {
return "simulations";
}

if (tags?.size === 1 && tags?.get("tx_hash") === "*") {
if (
operationName === "execute_tx" &&
tags?.size === 1 &&
tags?.get("tx") === "*Wasm(*"
) {
return "broadcasted";
}

Expand All @@ -56,15 +60,17 @@ export function DataTableToolbar<TData>({
onValueChange={(value) => {
switch (value) {
case "simulations": {
table.getColumn("operationName")?.setFilterValue("query");
table
.getColumn("tags")
?.setFilterValue(new Map([["raw_tx", "*"]]));
?.setFilterValue(new Map([["request", "*Bank(Send*"]]));
break;
}
case "broadcasted": {
table.getColumn("operationName")?.setFilterValue("execute_tx");
table
.getColumn("tags")
?.setFilterValue(new Map([["tx_hash", "*"]]));
?.setFilterValue(new Map([["tx", "*Wasm(*"]]));
break;
}
default: {
Expand Down Expand Up @@ -95,10 +101,8 @@ export function DataTableToolbar<TData>({
>
Custom filter
</SelectItem>
<SelectItem value="simulations">Failed simulations</SelectItem>
<SelectItem value="broadcasted">
Broadcasted transactions
</SelectItem>
<SelectItem value="simulations">Sim. Bank Send</SelectItem>
<SelectItem value="broadcasted">Execute WASM tx</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
Expand Down
27 changes: 14 additions & 13 deletions lib/mermaid.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Span } from "@/types/txs";
import { getAddressType } from "./chain";
import { getActorsFromOperations, getOperationsFromSpans } from "./parse-ron";

type TreeNode = {
id: string;
Expand Down Expand Up @@ -65,22 +66,22 @@ export function flowchartFromSpans(spans: Readonly<Array<Span>>) {
export function sequenceDiagramFromSpans(spans: Readonly<Array<Span>>) {
let chart = "sequenceDiagram";

for (const span of spans) {
const tx = span.tags.get("tx");

if (!tx || !tx.includes("Bank(Send")) {
continue;
}
const operations = getOperationsFromSpans(spans);
const actors = getActorsFromOperations(operations);

const sender = tx.match(/sender: (\w+)/)?.[1] ?? "";
const recipient = tx.match(/recipient: (\w+)/)?.[1] ?? "";

chart += `\n${getActorBox(sender)}`;
chart += `\n${getActorBox(recipient)}`;
for (const actor of actors) {
chart += `\n${getActorBox(actor)}`;
}

chart += `\n${sender}->>+${recipient}: <a href="/${span.traceId}/${span.spanId}">🏦 Send</a>`;
for (const operation of operations) {
const { label, isQuery, sender, recipient, traceId, spanId } = operation;
chart += `\n${sender}${isQuery ? "-" : ""}->>+${recipient}: <a href="/${traceId}/${spanId}">${label}</a>`;

break;
if (isQuery) {
chart += `\nactivate ${recipient}`;
chart += `\n${recipient}-->>+${sender}: <a class="hidden">response placeholder</a>`;
chart += `\ndeactivate ${recipient}`;
}
}

return chart;
Expand Down
204 changes: 204 additions & 0 deletions lib/parse-ron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { Span } from "@/types/txs";

type Operation = {
label: string;
isQuery: boolean;
sender: string;
recipient: string;
traceId: string;
spanId: string;
};

export const getActorsFromOperations = (
operations: readonly Operation[],
): readonly string[] =>
Array.from(
new Set(
operations.flatMap((operation) => [
operation.sender,
operation.recipient,
]),
),
);

export const getOperationsFromSpans = (
spans: Readonly<Array<Span>>,
): readonly Operation[] =>
spans
.map((span) => {
const txRon =
span.tags.get("request") || span.tags.get("msg") || span.tags.get("tx");
if (!txRon) {
return null;
}

switch (true) {
//NOTE - Avoids showing both execute_tx and sm.process_msg for bank queries
case txRon.includes("Bank(") &&
span.operationName === "sm.process_msg": {
return null;
}
case txRon.includes("Bank(Send"): {
return parseActorFromBankSendRon(txRon, span);
}
//NOTE - Avoids showing both execute_tx and sm.process_msg for wasm queries
case txRon.includes("Wasm(") &&
span.operationName === "sm.process_msg": {
return null;
}
case txRon.includes("Wasm(StoreCode"): {
return parseActorFromWasmStoreCodeRon(txRon, span);
}
case txRon.includes("Wasm(Instantiate"): {
return parseActorFromWasmInstantiateRon(txRon, span);
}
case txRon.includes("Wasm(Migrate"): {
return parseActorFromWasmMigrateRon(txRon, span);
}
case txRon.includes("Wasm(Execute"): {
return parseActorFromWasmExecuteRon(txRon, span);
}
case txRon.includes("Wasm(UpdateAdmin"): {
return parseActorFromWasmUpdateAdminRon(txRon, span);
}
default: {
return null;
}
}
})
.filter((operation): operation is Operation => !!operation);

const parseActorFromBankSendRon = (
bankSendRon: string,
{ traceId, spanId }: Span,
): Operation | null => {
const sender = bankSendRon.match(/sender: (\w+)/)?.[1];
const recipient = bankSendRon.match(/recipient: (\w+)/)?.[1];

if (!sender || !recipient) {
return null;
}

const isSimulation = bankSendRon.includes("Simulate(");

return {
label: isSimulation ? "💻🏦 Send" : "🏦 Send",
isQuery: isSimulation,
sender,
recipient,
traceId,
spanId,
};
};

const parseActorFromWasmStoreCodeRon = (
wasmStoreCodeRon: string,
{ traceId, spanId }: Span,
): Operation | null => {
const sender = wasmStoreCodeRon.match(/sender: (\w+)/)?.[1];

if (!sender) {
return null;
}

const isSimulation = wasmStoreCodeRon.includes("Simulate(");

return {
label: isSimulation ? "💻🕸 Store code" : "🕸 Store code",
isQuery: isSimulation,
sender,
recipient: sender,
traceId,
spanId,
};
};

const parseActorFromWasmInstantiateRon = (
wasmInstantiateRon: string,
{ traceId, spanId }: Span,
): Operation | null => {
const sender = wasmInstantiateRon.match(/sender: (\w+)/)?.[1];

if (!sender) {
return null;
}

const isSimulation = wasmInstantiateRon.includes("Simulate(");

return {
label: isSimulation ? "💻🕸 Instantiate" : "🕸 Instantiate",
isQuery: isSimulation,
sender,
recipient: sender,
traceId,
spanId,
};
};

const parseActorFromWasmMigrateRon = (
wasmMigrateRon: string,
{ traceId, spanId }: Span,
): Operation | null => {
const sender = wasmMigrateRon.match(/sender: (\w+)/)?.[1];

if (!sender) {
return null;
}

const isSimulation = wasmMigrateRon.includes("Simulate(");

return {
label: isSimulation ? "💻🕸 Migrate" : "🕸 Migrate",
isQuery: isSimulation,
sender,
recipient: sender,
traceId,
spanId,
};
};

const parseActorFromWasmExecuteRon = (
wasmExecuteRon: string,
{ traceId, spanId }: Span,
): Operation | null => {
const sender = wasmExecuteRon.match(/sender: (\w+)/)?.[1];
const contractAddr = wasmExecuteRon.match(/contract_addr: (\w+)/)?.[1];

if (!sender || !contractAddr) {
return null;
}

const isSimulation = wasmExecuteRon.includes("Simulate(");

return {
label: isSimulation ? "💻🕸 Execute" : "🕸 Execute",
isQuery: isSimulation,
sender,
recipient: contractAddr,
traceId,
spanId,
};
};

const parseActorFromWasmUpdateAdminRon = (
wasmUpdateAdminRon: string,
{ traceId, spanId }: Span,
): Operation | null => {
const sender = wasmUpdateAdminRon.match(/sender: (\w+)/)?.[1];
const contractAddr = wasmUpdateAdminRon.match(/contract_addr: (\w+)/)?.[1];

if (!sender || !contractAddr) {
return null;
}

const isSimulation = wasmUpdateAdminRon.includes("Simulate(");

return {
label: isSimulation ? "💻🕸 Update admin" : "🕸 Update admin",
isQuery: isSimulation,
sender,
recipient: contractAddr,
traceId,
spanId,
};
};
Loading