Skip to content

Commit

Permalink
feat(vm): add tally vm adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
FranklinWaller committed Jun 4, 2024
1 parent 4e8b4f0 commit 3b40b20
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 13 deletions.
9 changes: 6 additions & 3 deletions libs/as-sdk-integration-tests/assembly/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { httpFetch, Process } from '../../as-sdk/assembly/index';
import { testTallyVmHttp, testTallyVmMode } from './vm-tests';

const args = Process.args().at(1);

if (args === 'testHttpRejection') {
testHttpRejection();
} else if (args === 'testHttpSuccess') {
testHttpSuccess();
} else if (args === 'testTallyVmMode') {
testTallyVmMode();
} else if (args === 'testTallyVmHttp') {
testTallyVmHttp();
}

export function testHttpRejection(): void {
Expand All @@ -18,7 +23,7 @@ export function testHttpRejection(): void {

Process.exit_with_result(0, buffer);
} else {
Process.exit_with_message(1, "Test failed");
Process.exit_with_message(1, 'Test failed');
}
}

Expand All @@ -35,5 +40,3 @@ export function testHttpSuccess(): void {
Process.exit_with_message(31, 'My custom test failed');
}
}


28 changes: 28 additions & 0 deletions libs/as-sdk-integration-tests/assembly/vm-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Process, httpFetch } from '../../as-sdk/assembly/index';

export function testTallyVmMode(): void {
const envs = Process.envs();
const vmMode = envs.get('VM_MODE');

if (vmMode === 'tally') {
Process.exit_with_message(0, 'tally');
} else {
Process.exit_with_message(1, 'dr');
}
}

export function testTallyVmHttp(): void {
const response = httpFetch('https://swapi.dev/api/planets/1/');
const fulfilled = response.fulfilled;
const rejected = response.rejected;

if (fulfilled !== null) {
Process.exit_with_message(1, 'this should not be allowed in tally mode');
}

if (rejected !== null) {
Process.exit_with_result(0, rejected.bytes);
}
}


1 change: 1 addition & 0 deletions libs/as-sdk-integration-tests/src/http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jest.setTimeout(15_000);

const TestVmAdapter = jest.fn().mockImplementation(() => {
return {
modifyVmCallData: (v) => v,
setProcessId: () => {},
httpFetch: mockHttpFetch
};
Expand Down
40 changes: 40 additions & 0 deletions libs/as-sdk-integration-tests/src/tallyvm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { TallyVmAdapter, callVm } from '../../../dist/libs/vm';
import { readFile } from 'node:fs/promises';

describe('TallyVm', () => {
it('should run in tally vm mode', async () => {
const wasmBinary = await readFile(
'dist/libs/as-sdk-integration-tests/debug.wasm'
);
const result = await callVm(
{
args: ['testTallyVmMode'],
envs: {},
binary: new Uint8Array(wasmBinary),
},
undefined,
new TallyVmAdapter()
);

expect(result.resultAsString).toEqual('tally');
expect(result.exitCode).toBe(0);
});

it('should fail to make an http call', async () => {
const wasmBinary = await readFile(
'dist/libs/as-sdk-integration-tests/debug.wasm'
);
const result = await callVm(
{
args: ['testTallyVmHttp'],
envs: {},
binary: new Uint8Array(wasmBinary),
},
undefined,
new TallyVmAdapter()
);

expect(result.resultAsString).toEqual('http_fetch is not allowed in tally');
expect(result.exitCode).toBe(0);
});
});
24 changes: 23 additions & 1 deletion libs/as-sdk/assembly/process.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Process as WasiProcess, CommandLine } from 'as-wasi/assembly';
import { Process as WasiProcess, CommandLine, Environ } from 'as-wasi/assembly';
import { execution_result } from './bindings/seda_v1';

export default class Process {
Expand All @@ -17,6 +17,28 @@ export default class Process {
return CommandLine.all;
}

/**
* Gets all the environment variables as a Map
*
* @returns {Map<string, string>} key, value pair with all environment variables
* @example
* ```ts
* const env = Process.env();
*
* const vmMode = env.get('VM_MODE');
* ```
*/
static envs(): Map<string, string> {
const result: Map<string, string> = new Map();

for (let i: i32 = 0; i < Environ.all.length; i++) {
const entry = Environ.all[i];
result.set(entry.key, entry.value);
}

return result;
}

/**
* Exits the process with a message
* This sets the result of the Data Request execution to the message
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import type { HttpFetchAction } from './types/vm-actions';
import type { HttpFetchAction } from './types/vm-actions.js';
import { HttpFetchResponse } from './types/vm-actions.js';
import type { VmAdapter } from './types/vm-adapter';
import type { VmAdapter } from './types/vm-adapter.js';
import fetch from 'node-fetch';
import { PromiseStatus } from './types/vm-promise.js';
import { VmCallData } from './vm.js';

export default class DefaultVmAdapter implements VmAdapter {
export default class DataRequestVmAdapter implements VmAdapter {
private processId?: string;

modifyVmCallData(input: VmCallData): VmCallData {
return {
...input,
envs: {
...input.envs,
VM_MODE: 'dr',
},
};
}

setProcessId(processId: string) {
this.processId = processId;
}
Expand Down
17 changes: 12 additions & 5 deletions libs/vm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { Worker } from "node:worker_threads";
import type { VmCallData, VmResult } from "./vm";
import { VmCallWorkerMessage, WorkerMessage, WorkerMessageType } from "./types/worker-messages.js";
import type { VmAdapter } from "./types/vm-adapter.js";
import DefaultVmAdapter from "./default-vm-adapter.js";
import DataRequestVmAdapter from "./data-request-vm-adapter.js";
import { parse, format } from "node:path";
import { HostToWorker } from "./worker-host-communication.js";
import { createProcessId } from "./services/create-process-id.js";

export { default as TallyVmAdapter } from './tally-vm-adapter.js';
export { default as DataRequestVmAdapter } from './data-request-vm-adapter.js';

const CURRENT_FILE_PATH = parse(import.meta.url);
CURRENT_FILE_PATH.base = 'worker.js';
const DEFAULT_WORKER_PATH = format(CURRENT_FILE_PATH);
Expand All @@ -19,13 +22,17 @@ const DEFAULT_WORKER_PATH = format(CURRENT_FILE_PATH);
* @param vmAdapter Option to insert a custom VM adapter, can be used to mock
* @returns
*/
export function callVm(callData: VmCallData, workerUrl = DEFAULT_WORKER_PATH, vmAdapter: VmAdapter = new DefaultVmAdapter()): Promise<VmResult> {
export function callVm(
callData: VmCallData,
workerUrl = DEFAULT_WORKER_PATH,
vmAdapter: VmAdapter = new DataRequestVmAdapter()
): Promise<VmResult> {
return new Promise((resolve) => {
const finalCallData: VmCallData = {
const finalCallData: VmCallData = vmAdapter.modifyVmCallData({
...callData,
// First argument matches the Rust Wasmer standard (_start for WASI)
args: ['_start', ...callData.args],
};
});

const processId = createProcessId(finalCallData);
vmAdapter.setProcessId(processId);
Expand Down Expand Up @@ -74,7 +81,7 @@ export function callVm(callData: VmCallData, workerUrl = DEFAULT_WORKER_PATH, vm
exitCode,
stderr: `[${processId}] - The worker has been terminated`,
stdout: '',
})
});
});

worker.postMessage(workerMessage);
Expand Down
40 changes: 40 additions & 0 deletions libs/vm/src/tally-vm-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { HttpFetchAction } from './types/vm-actions';
import { HttpFetchResponse } from './types/vm-actions.js';
import type { VmAdapter } from './types/vm-adapter';
import { PromiseStatus } from './types/vm-promise.js';
import type { VmCallData } from './vm';

export default class TallyVmAdapter implements VmAdapter {
private processId?: string;

modifyVmCallData(input: VmCallData): VmCallData {
return {
...input,
envs: {
...input.envs,
VM_MODE: 'tally'
}
};
}

setProcessId(processId: string) {
this.processId = processId;
}

async httpFetch(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_action: HttpFetchAction
): Promise<PromiseStatus<HttpFetchResponse>> {
const error = new TextEncoder().encode('http_fetch is not allowed in tally');

return PromiseStatus.rejected(
new HttpFetchResponse({
bytes: Array.from(error),
content_length: error.length,
headers: {},
status: 0,
url: '',
})
);
}
}
20 changes: 20 additions & 0 deletions libs/vm/src/types/vm-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import type { VmCallData } from "../vm";
import type { HttpFetchAction, HttpFetchResponse } from "./vm-actions";
import { PromiseStatus } from "./vm-promise.js";


export interface VmAdapter {
/**
* Allows the adapter to modify the call data before executing
* this can be used to inject arguments, environment variables, etc.
*
* @param input
*/
modifyVmCallData(input: VmCallData): VmCallData;

/**
* Sets the process id in order to identify a vm call in the logs
*
* @param processId
*/
setProcessId(processId: string): void,

/**
* Method to do a remote http fetch call
*
* @param action
*/
httpFetch(action: HttpFetchAction): Promise<PromiseStatus<HttpFetchResponse>>;
}
1 change: 0 additions & 1 deletion libs/vm/src/vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export interface VmResult {
}

export async function executeVm(callData: VmCallData, notifierBuffer: SharedArrayBuffer, processId: string): Promise<VmResult> {

await init();
const wasi = new WASI({
args: callData.args,
Expand Down

0 comments on commit 3b40b20

Please sign in to comment.