First working prototype with call tree

This commit is contained in:
Willian Mitsuda 2021-10-26 22:10:37 -03:00
parent 75612fc3cc
commit 1b9f26a3f0
4 changed files with 203 additions and 0 deletions

View File

@ -25,6 +25,12 @@ const Logs = React.lazy(
/* webpackChunkName: "txlogs", webpackPrefetch: true */ "./transaction/Logs"
)
);
const Trace = React.lazy(
() =>
import(
/* webpackChunkName: "txtrace", webpackPrefetch: true */ "./transaction/Trace"
)
);
type TransactionParams = {
txhash: string;
@ -87,6 +93,7 @@ const Transaction: React.FC = () => {
{txData && ` (${txData.confirmedData?.logs?.length ?? 0})`}
</NavTab>
)}
<NavTab href={`/tx/${txhash}/trace`}>Trace</NavTab>
</Tab.List>
</Tab.Group>
<React.Suspense fallback={null}>
@ -105,6 +112,9 @@ const Transaction: React.FC = () => {
<Route path="/tx/:txhash/logs/" exact>
<Logs txData={txData} metadata={metadata} />
</Route>
<Route path="/tx/:txhash/trace" exact>
<Trace txData={txData} />
</Route>
</Switch>
</React.Suspense>
</SelectionContext.Provider>

39
src/transaction/Trace.tsx Normal file
View File

@ -0,0 +1,39 @@
import React, { useContext } from "react";
import AddressHighlighter from "../components/AddressHighlighter";
import DecoratedAddressLink from "../components/DecoratedAddressLink";
import ContentFrame from "../ContentFrame";
import { TransactionData } from "../types";
import { useTraceTransaction } from "../useErigonHooks";
import { RuntimeContext } from "../useRuntime";
import TraceItem from "./TraceItem";
type TraceProps = {
txData: TransactionData;
};
const Trace: React.FC<TraceProps> = ({ txData }) => {
const { provider } = useContext(RuntimeContext);
const traces = useTraceTransaction(provider, txData.transactionHash);
return (
<ContentFrame tabs>
<div className="mt-4 mb-5 space-y-3 font-code text-sm flex flex-col items-start">
<div>
<AddressHighlighter address={txData.from}>
<DecoratedAddressLink
address={txData.from}
miner={txData.from === txData.confirmedData?.miner}
txFrom
txTo={txData.from === txData.to}
/>
</AddressHighlighter>
</div>
{traces?.map((t, i, a) => (
<TraceItem key={i} t={t} txData={txData} last={i === a.length - 1} />
))}
</div>
</ContentFrame>
);
};
export default React.memo(Trace);

View File

@ -0,0 +1,68 @@
import React from "react";
import AddressHighlighter from "../components/AddressHighlighter";
import DecoratedAddressLink from "../components/DecoratedAddressLink";
import { TransactionData } from "../types";
import { rawInputTo4Bytes } from "../use4Bytes";
import { TraceGroup } from "../useErigonHooks";
type TraceItemProps = {
t: TraceGroup;
txData: TransactionData;
last: boolean;
};
const TraceItem: React.FC<TraceItemProps> = ({ t, txData, last }) => {
const raw4Bytes = rawInputTo4Bytes(t.input);
return (
<>
<div className="flex">
<div className="relative w-5">
<div className="absolute border-l border-b w-full h-full transform -translate-y-1/2"></div>
{!last && (
<div className="absolute left-0 border-l w-full h-full transform translate-y-1/2"></div>
)}
</div>
<div className="flex items-baseline border rounded px-1 py-px">
<span className="mr-2">{t.depth}</span>
<span className="text-xs text-gray-400 lowercase">{t.type}</span>
<span>
<AddressHighlighter address={t.to}>
<DecoratedAddressLink
address={t.to}
miner={t.to === txData.confirmedData?.miner}
txFrom={t.to === txData.from}
txTo={t.to === txData.to}
/>
</AddressHighlighter>
</span>
<span>.</span>
<span>{raw4Bytes}</span>
<span>(</span>
{t.input.length > 10 && (
<span className="whitespace-nowrap">
input=[{t.input.slice(10)}]
</span>
)}
<span>)</span>
</div>
</div>
{t.children && (
<div className="flex">
<div className={`w-10 ${last ? "" : "border-l"}`}></div>
<div className="space-y-3">
{t.children.map((tc, i, a) => (
<TraceItem
key={i}
t={tc}
txData={txData}
last={i === a.length - 1}
/>
))}
</div>
</div>
)}
</>
);
};
export default React.memo(TraceItem);

View File

@ -308,3 +308,89 @@ export const useInternalOperations = (
return intTransfers;
};
export type TraceEntry = {
type: string;
depth: number;
from: string;
to: string;
value: BigNumber;
input: string;
};
export type TraceGroup = TraceEntry & {
children: TraceGroup[] | null;
};
export const useTraceTransaction = (
provider: JsonRpcProvider | undefined,
txHash: string
): TraceGroup[] | undefined => {
const [traceGroups, setTraceGroups] = useState<TraceGroup[] | undefined>();
useEffect(() => {
if (!provider) {
setTraceGroups(undefined);
return;
}
const traceTx = async () => {
const results = await provider.send("ots_traceTransaction", [txHash]);
// Implement better formatter
for (let i = 0; i < results.length; i++) {
results[i].from = provider.formatter.address(results[i].from);
results[i].to = provider.formatter.address(results[i].to);
}
// Build trace tree
const buildTraceTree = (
flatList: TraceEntry[],
depth: number = 0
): TraceGroup[] => {
const entries: TraceGroup[] = [];
let children: TraceEntry[] | null = null;
for (let i = 0; i < flatList.length; i++) {
if (flatList[i].depth === depth) {
if (children !== null) {
const childrenTree = buildTraceTree(children, depth + 1);
const prev = entries.pop();
if (prev) {
prev.children = childrenTree;
entries.push(prev);
}
}
entries.push({
...flatList[i],
children: null,
});
children = null;
} else {
if (children === null) {
children = [];
}
children.push(flatList[i]);
}
}
if (children !== null) {
const childrenTree = buildTraceTree(children, depth + 1);
const prev = entries.pop();
if (prev) {
prev.children = childrenTree;
entries.push(prev);
}
}
return entries;
};
const traceTree = buildTraceTree(results);
setTraceGroups(traceTree);
};
traceTx();
}, [provider, txHash]);
return traceGroups;
};