@@ -46,7 +51,7 @@ export const Links = () => {
-
+
@@ -65,6 +70,32 @@ export const Links = () => {
+
+
+
+
+
+
+
Ecalli Trace file (.log, .trace)
+
+
+ Load a trace file with embedded program and host call data. See{" "}
+
+ specification
+
+
+
+
+
+
);
};
diff --git a/src/components/ProgramLoader/Loader.tsx b/src/components/ProgramLoader/Loader.tsx
index 804addd5..5d995deb 100644
--- a/src/components/ProgramLoader/Loader.tsx
+++ b/src/components/ProgramLoader/Loader.tsx
@@ -4,14 +4,14 @@ import { useState, useCallback, useEffect } from "react";
import { ProgramUploadFileOutput } from "./types";
import { useDebuggerActions } from "@/hooks/useDebuggerActions";
import { useAppDispatch, useAppSelector } from "@/store/hooks.ts";
-import { setIsProgramEditMode, setSpiArgs, setServiceId } from "@/store/debugger/debuggerSlice.ts";
-import { selectIsAnyWorkerLoading, setAllWorkersServiceId } from "@/store/workers/workersSlice";
+import { setIsProgramEditMode, setSpiArgs, setServiceId, setHostCallsTrace } from "@/store/debugger/debuggerSlice.ts";
+import { selectIsAnyWorkerLoading } from "@/store/workers/workersSlice";
import { isSerializedError } from "@/store/utils";
import { ProgramFileUpload } from "@/components/ProgramLoader/ProgramFileUpload.tsx";
import { useNavigate } from "react-router";
import { Links } from "./Links";
import { Separator } from "../ui/separator";
-import { TriangleAlert } from "lucide-react";
+import { TriangleAlert, FileText } from "lucide-react";
import { bytes } from "@typeberry/lib";
import {
EntrypointSelector,
@@ -22,6 +22,7 @@ import {
} from "./EntrypointSelector";
import { loadSpiConfig, saveSpiConfig } from "./spiConfig";
import { encodeRefineParams, encodeAccumulateParams, encodeIsAuthorizedParams } from "./spiEncoding";
+import { getTraceSummary, parseTrace } from "@/lib/host-call-trace";
type LoaderStep = "upload" | "entrypoint";
@@ -30,6 +31,8 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
const [programLoad, setProgramLoad] = useState
();
const [error, setError] = useState();
const [currentStep, setCurrentStep] = useState("upload");
+ const [traceContent, setTraceContent] = useState(null);
+ const [traceSummary, setTraceSummary] = useState | null>(null);
// Load saved config once on mount
const savedConfig = loadSpiConfig();
@@ -58,6 +61,31 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
setCurrentStep("upload");
}, [programLoad]);
+ // Auto-switch to RAW mode when a trace is loaded
+ useEffect(() => {
+ if (traceContent !== null) {
+ setIsManualMode(true);
+ }
+ }, [traceContent]);
+
+ const handleFileUpload = useCallback((output: ProgramUploadFileOutput, trace?: string) => {
+ setProgramLoad(output);
+ if (trace) {
+ try {
+ setTraceContent(trace);
+ const parsed = parseTrace(trace);
+ setTraceSummary(getTraceSummary(parsed));
+ } catch (e) {
+ console.error("Failed to parse trace:", e);
+ setTraceContent(null);
+ setTraceSummary(null);
+ }
+ } else {
+ setTraceContent(null);
+ setTraceSummary(null);
+ }
+ }, []);
+
// Auto-encode parameters when they change and update PC
useEffect(() => {
if (!isSpiProgram) return;
@@ -99,11 +127,13 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
// For SPI programs, update PC and encode parameters
if (loadedProgram?.spiProgram) {
- // Parse the encoded SPI arguments (either auto-generated or manually entered)
- const parsedArgs = bytes.BytesBlob.parseBlob(encodedSpiArgs);
+ // Use SPI args from trace if available, otherwise use auto-generated/manual ones
+ const spiArgsToUse = loadedProgram.spiArgs
+ ? loadedProgram.spiArgs
+ : bytes.BytesBlob.parseBlob(encodedSpiArgs).raw;
- // Set the encoded parameters as SPI arguments
- dispatch(setSpiArgs(parsedArgs.raw));
+ // Set the SPI arguments
+ dispatch(setSpiArgs(spiArgsToUse));
// Extract and set service ID from entrypoint parameters
let serviceId: number;
@@ -119,7 +149,6 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
break;
}
dispatch(setServiceId(serviceId));
- await dispatch(setAllWorkersServiceId()).unwrap();
// Update the initial state with the correct PC for the entrypoint
const pc = parseInt(manualPc, 10) || 0;
@@ -134,6 +163,10 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
await debuggerActions.handleProgramLoad(modifiedProgram);
+ if (traceContent) {
+ dispatch(setHostCallsTrace(traceContent));
+ }
+
// Save SPI configuration to localStorage after successful load
if (loadedProgram?.spiProgram) {
saveSpiConfig({
@@ -170,11 +203,14 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
accumulateParams,
isAuthorizedParams,
isManualMode,
+ traceContent,
],
);
+ const hasTraceWithEntrypoint = traceContent !== null;
+
const handleNextStep = () => {
- if (currentStep === "upload" && isSpiProgram) {
+ if (currentStep === "upload" && isSpiProgram && !hasTraceWithEntrypoint) {
setCurrentStep("entrypoint");
} else {
handleLoad();
@@ -196,9 +232,9 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
{currentStep === "upload" && (
<>
{
- setProgramLoad(val);
- // For non-SPI programs, load directly. For SPI programs, go to entrypoint selection
+ handleFileUpload(val);
if (val.spiProgram === null) {
handleLoad(val);
}
@@ -206,7 +242,12 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
/>
{error && (
@@ -230,6 +271,18 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
{JSON.stringify(programLoad.initial, null, 2)}