diff --git a/src/packages/web-worker/command-handlers/host-call.ts b/src/packages/web-worker/command-handlers/host-call.ts index d44ee33..cd1ef90 100644 --- a/src/packages/web-worker/command-handlers/host-call.ts +++ b/src/packages/web-worker/command-handlers/host-call.ts @@ -5,6 +5,7 @@ import { WriteAccounts } from "@/packages/host-calls/write"; import { isInternalPvm } from "../utils"; import { ReadAccounts } from "@/packages/host-calls/read"; import { tryAsServiceId } from "@typeberry/block"; +import { Memory, MemoryIndex, SbrkIndex, tryAsGas } from "@typeberry/pvm-debugger-adapter"; type HostCallParams = { pvm: PvmApiInterface | null; @@ -26,9 +27,96 @@ type HostCallResponse = error?: unknown; }; -type ExecuteParams = Parameters; -type RegistersType = ExecuteParams[1]; -type MemoryType = ExecuteParams[2]; +// [KrFr] Gas counter is not used. Can be mocked for now. +const getGasCounter = (pvm: PvmApiInterface) => { + if (isInternalPvm(pvm)) { + return pvm.getInterpreter().getGasCounter(); + } + + return { + get: () => { + return tryAsGas(10_000); + }, + set: () => {}, + sub: () => { + return false; + }, + }; +}; + +const getRegisters = (pvm: PvmApiInterface) => { + if (isInternalPvm(pvm)) { + return pvm.getInterpreter().getRegisters(); + } + + return { + getU32: (registerIndex: number) => { + return pvm.getRegisters()[registerIndex]; + }, + setU32: (registerIndex: number, value: number) => { + const registers = pvm.getRegisters(); + registers[registerIndex] = value; + pvm.setRegisters(registers); + }, + + // [KrFr] The following functions are not used in the read and write host call handlers. Can be mocked for now. + /* eslint-disable @typescript-eslint/no-unused-vars */ + getI32: (_registerIndex: number) => { + return 0; + }, + setI32: (_registerIndex: number, _value: number) => {}, + + getU64: (_registerIndex: number) => { + return BigInt(0); + }, + getI64: (_registerIndex: number) => { + return BigInt(0); + }, + setU64: (_registerIndex: number, _value: bigint) => {}, + setI64: (_registerIndex: number, _value: bigint) => {}, + /* eslint-enable @typescript-eslint/no-unused-vars */ + }; +}; + +const getMemory = (pvm: PvmApiInterface) => { + if (isInternalPvm(pvm)) { + return pvm.getInterpreter().getMemory(); + } + + return { + loadInto: (result: Uint8Array, startAddress: MemoryIndex) => { + result = pvm.getPageDump(startAddress); + return null; + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isWriteable: (_destinationStart: MemoryIndex, _length: number) => { + return true; // Simulates that the memory slice is writeable + }, + storeFrom: (address: MemoryIndex, bytes: Uint8Array) => { + pvm.setMemory(address, bytes); + return null; + }, + + // [KrFr] The following functions are not used in the read and write host call handlers. Can be mocked for now. + /* eslint-disable @typescript-eslint/no-unused-vars */ + fromInitialMemory: () => { + return new Memory(); // Returns the mock itself for simplicity + }, + reset: () => {}, + copyFrom: (_memory: Memory) => {}, + sbrk: (_length: number) => { + return 0 as SbrkIndex; // Simulates returning the current sbrk index + }, + getPageDump: (_pageNumber: number) => { + return null; // Simulates no page dump available + }, + getDirtyPages: function* () { + // Simulates an empty iterator + yield* []; + }, + /* eslint-enable @typescript-eslint/no-unused-vars */ + }; +}; const hostCall = async ({ pvm, @@ -41,32 +129,21 @@ const hostCall = async ({ storage: Storage; serviceId: number; }): Promise => { - if (!isInternalPvm(pvm)) { - return { hostCallIdentifier, status: CommandStatus.ERROR, error: new Error("External PVM not supported") }; - } - if (hostCallIdentifier === HostCallIdentifiers.READ) { const readAccounts = new ReadAccounts(storage); const jamHostCall = new read.Read(readAccounts); // TODO the types are the same, but exported from different packages and lost track of the type jamHostCall.currentServiceId = tryAsServiceId(serviceId) as unknown as typeof jamHostCall.currentServiceId; - await jamHostCall.execute( - pvm.getInterpreter().getGasCounter(), - pvm.getInterpreter().getRegisters() as unknown as RegistersType, - pvm.getInterpreter().getMemory() as unknown as MemoryType, - ); + await jamHostCall.execute(getGasCounter(pvm), getRegisters(pvm), getMemory(pvm)); return { hostCallIdentifier, status: CommandStatus.SUCCESS }; } else if (hostCallIdentifier === HostCallIdentifiers.WRITE) { const writeAccounts = new WriteAccounts(storage); const jamHostCall = new write.Write(writeAccounts); - await jamHostCall.execute( - pvm.getInterpreter().getGasCounter(), - pvm.getInterpreter().getRegisters() as unknown as RegistersType, - pvm.getInterpreter().getMemory() as unknown as MemoryType, - ); + await jamHostCall.execute(getGasCounter(pvm), getRegisters(pvm), getMemory(pvm)); + return { hostCallIdentifier, storage, status: CommandStatus.SUCCESS }; } diff --git a/src/store/workers/workersSlice.ts b/src/store/workers/workersSlice.ts index 534f91a..c6d2d74 100644 --- a/src/store/workers/workersSlice.ts +++ b/src/store/workers/workersSlice.ts @@ -288,33 +288,30 @@ export const handleHostCall = createAsyncThunk("workers/handleHostCall", async ( } await Promise.all( - state.workers - // [KF] Remove or change this condition when we support host calls on more PVMs. - .filter(({ id }) => id === "typeberry") - .map(async (worker) => { - const resp = await asyncWorkerPostMessage(worker.id, worker.worker, { - command: Commands.HOST_CALL, - payload: { hostCallIdentifier: worker.exitArg as HostCallIdentifiers }, - }); - - if ( - resp.payload.hostCallIdentifier === HostCallIdentifiers.WRITE && - resp.payload.storage && - // Remove if we decide to make storage initialization optional - state.debugger.storage - ) { - const newStorage = mergePVMAndDebuggerEcalliStorage(resp.payload.storage, state.debugger.storage); - dispatch(setStorage(newStorage)); - } + state.workers.map(async (worker) => { + const resp = await asyncWorkerPostMessage(worker.id, worker.worker, { + command: Commands.HOST_CALL, + payload: { hostCallIdentifier: worker.exitArg as HostCallIdentifiers }, + }); - if ((getState() as RootState).debugger.isRunMode) { - dispatch(continueAllWorkers()); - } + if ( + resp.payload.hostCallIdentifier === HostCallIdentifiers.WRITE && + resp.payload.storage && + // Remove if we decide to make storage initialization optional + state.debugger.storage + ) { + const newStorage = mergePVMAndDebuggerEcalliStorage(resp.payload.storage, state.debugger.storage); + dispatch(setStorage(newStorage)); + } - if (hasCommandStatusError(resp)) { - throw new Error(resp.error.message); - } - }), + if ((getState() as RootState).debugger.isRunMode) { + dispatch(continueAllWorkers()); + } + + if (hasCommandStatusError(resp)) { + throw new Error(resp.error.message); + } + }), ); if (selectShouldContinueRunning(state)) {