-
Notifications
You must be signed in to change notification settings - Fork 9
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
QuickJSPlugin: read external data #33
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { QuickJSContext, QuickJSHandle, QuickJSRuntime, QuickJSWASMModule } from 'quickjs-emscripten'; | ||
import {AoInteractionResult, InteractionResult, LoggerFactory, QuickJsPluginMessage, Tag} from 'warp-contracts'; | ||
import { AoInteractionResult, InteractionResult, LoggerFactory, QuickJsPluginMessage, Tag } from 'warp-contracts'; | ||
import { errorEvalAndDispose } from './utils'; | ||
|
||
export class QuickJsHandlerApi<State> { | ||
|
@@ -8,14 +8,18 @@ export class QuickJsHandlerApi<State> { | |
constructor( | ||
private readonly vm: QuickJSContext, | ||
private readonly runtime: QuickJSRuntime, | ||
private readonly quickJS: QuickJSWASMModule, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the QuickJSWASMModule import can also now be removed? |
||
private readonly isSourceAsync: boolean | ||
) {} | ||
|
||
async handle<Result>(message: QuickJsPluginMessage, env: ProcessEnv, state?: State): Promise<InteractionResult<State, Result>> { | ||
async handle<Result>( | ||
message: QuickJsPluginMessage, | ||
env: ProcessEnv, | ||
state?: State | ||
): Promise<InteractionResult<State, Result>> { | ||
if (state) { | ||
this.initState(state); | ||
} | ||
return this.runContractFunction(message, env); | ||
return await this.runContractFunction(message, env); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. technically await is not needed here |
||
} | ||
|
||
initState(state: State): void { | ||
|
@@ -27,13 +31,21 @@ export class QuickJsHandlerApi<State> { | |
} | ||
} | ||
|
||
private async runContractFunction<Result>(message: QuickJsPluginMessage, env: ProcessEnv): InteractionResult<State, Result> { | ||
private async runContractFunction<Result>( | ||
message: QuickJsPluginMessage, | ||
env: ProcessEnv | ||
): InteractionResult<State, Result> { | ||
try { | ||
const evalInteractionResult = this.vm.evalCode(`__handleDecorator(${JSON.stringify(message)}, ${JSON.stringify(env)})`); | ||
const evalInteractionResult = this.isSourceAsync | ||
? await this.evalInteractionAsync(message, env) | ||
: this.evalInteractionSync(message, env); | ||
if (evalInteractionResult.error) { | ||
errorEvalAndDispose('interaction', this.logger, this.vm, evalInteractionResult.error); | ||
} else { | ||
const result: AoInteractionResult<Result> = this.disposeResult(evalInteractionResult); | ||
if (this.isSourceAsync) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it required here? shouldn't it be only called on the host function side? |
||
this.vm.runtime.executePendingJobs(); | ||
} | ||
const state = this.currentState() as State; | ||
return { | ||
Memory: null, | ||
|
@@ -69,6 +81,20 @@ export class QuickJsHandlerApi<State> { | |
} | ||
} | ||
|
||
private async evalInteractionAsync(message: QuickJsPluginMessage, env: ProcessEnv) { | ||
const result = this.vm.evalCode(`(async () => { | ||
return await __handleDecorator(${JSON.stringify(message)}, ${JSON.stringify(env)}) | ||
})()`); | ||
const promiseHandle = this.vm.unwrapResult(result); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how are erros how handled? e.g. what if dry-run timeouts? |
||
const evalInteractionResult = await this.vm.resolvePromise(promiseHandle); | ||
promiseHandle.dispose(); | ||
return evalInteractionResult; | ||
} | ||
|
||
private evalInteractionSync(message: QuickJsPluginMessage, env: ProcessEnv) { | ||
return this.vm.evalCode(`__handleDecorator(${JSON.stringify(message)}, ${JSON.stringify(env)})`); | ||
} | ||
|
||
currentBinaryState(state: State): Buffer { | ||
const currentState = state || this.currentState(); | ||
return Buffer.from(JSON.stringify(currentState)); | ||
|
@@ -99,19 +125,18 @@ export class QuickJsHandlerApi<State> { | |
resultValue.dispose(); | ||
return result; | ||
} | ||
|
||
} | ||
|
||
// https://cookbook_ao.g8way.io/concepts/processes.html | ||
export type ProcessEnv = { | ||
Process: { | ||
Id: string, | ||
Owner: string, | ||
Tags: { name: string, value: string }[] | ||
}, | ||
Id: string; | ||
Owner: string; | ||
Tags: { name: string; value: string }[]; | ||
}; | ||
Module: { | ||
Id: string, | ||
Owner: string, | ||
Tags: { name: string, value: string }[] | ||
} | ||
} | ||
Id: string; | ||
Owner: string; | ||
Tags: { name: string; value: string }[]; | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import { QuickJSContext } from 'quickjs-emscripten'; | ||
import { LoggerFactory } from 'warp-contracts'; | ||
import { QuickJSContext, QuickJSHandle } from 'quickjs-emscripten'; | ||
import { HandlerBasedContract, LoggerFactory } from 'warp-contracts'; | ||
import { PNG } from 'pngjs'; | ||
import seedrandom from 'seedrandom'; | ||
import { SignedDataPackage } from "@redstone-finance/protocol" | ||
import { SignedDataPackage } from '@redstone-finance/protocol'; | ||
|
||
export class QuickJsEvaluator { | ||
private readonly logger = LoggerFactory.INST.create('QuickJsEvaluator'); | ||
|
@@ -39,7 +39,7 @@ export class QuickJsEvaluator { | |
const randomHandle = this.vm.newFunction('random', (...args) => { | ||
const nativeArgs = args.map(this.vm.dump); | ||
const message = nativeArgs[0]; | ||
const uniqueValue = nativeArgs.length > 1 ? "" + nativeArgs[1] : '' | ||
const uniqueValue = nativeArgs.length > 1 ? '' + nativeArgs[1] : ''; | ||
const rng = seedrandom(message.Signature + uniqueValue); | ||
return this.vm.newNumber(rng()); | ||
}); | ||
|
@@ -50,6 +50,42 @@ export class QuickJsEvaluator { | |
randomHandle.dispose(); | ||
} | ||
|
||
dummyPromiseEval() { | ||
const dummyPromiseEval = this.vm.newFunction('dummyPromise', () => { | ||
const promise = this.vm.newPromise(); | ||
promise.resolve(this.vm.newString('')); | ||
promise.settled.then(this.vm.runtime.executePendingJobs); | ||
return promise.handle; | ||
}); | ||
this.vm.setProp(this.vm.global, 'dummyPromise', dummyPromiseEval); | ||
dummyPromiseEval.dispose(); | ||
} | ||
|
||
evalExternal() { | ||
const readExternalHandle = this.vm.newFunction('readExternal', (processIdHandle, actionHandle) => { | ||
const promise = this.vm.newPromise(); | ||
this.readExternal(processIdHandle, actionHandle).then((result) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't we also somehow handle errors here and pass some error info to the quickjs? |
||
promise.resolve(this.vm.newString(JSON.stringify(result) || '')); | ||
}); | ||
promise.settled.then(this.vm.runtime.executePendingJobs); | ||
return promise.handle; | ||
}); | ||
this.vm.setProp(this.vm.global, 'readExternal', readExternalHandle); | ||
readExternalHandle.dispose(); | ||
} | ||
|
||
async readExternal(processIdHandle: QuickJSHandle, actionHandle: QuickJSHandle) { | ||
const processId = this.vm.getString(processIdHandle); | ||
const action = this.vm.getString(actionHandle); | ||
const { dryrun } = await import('@permaweb/aoconnect'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why dynamic import? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. already discussed |
||
const readRes = await dryrun({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is the default timeout for the dryrun? maybe we should it limit to sth. like 10s? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont know what's the default but i've added some logic so the error is thrown if dryrun is executing more than 10s |
||
process: processId, | ||
tags: [{ name: 'Action', value: action }], | ||
data: '1234' | ||
}); | ||
return JSON.parse(readRes.Messages[0].Data); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe there is no point in parsing the response if we need to stringify it again to pass it to the quickjs.. |
||
} | ||
|
||
evalRedStone() { | ||
const recoverSignerAddressHandle = this.vm.newFunction('recoverSignerAddress', (...args) => { | ||
const nativeArgs = args.map(this.vm.dump); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ import { | |
newVariant | ||
} from 'quickjs-emscripten'; | ||
import { QuickJsHandlerApi } from './QuickJsHandlerApi'; | ||
import { decorateProcessFn } from './eval/evalCode/decorator'; | ||
import { asyncDecorateProcessFn, decorateProcessFn } from './eval/evalCode/decorator'; | ||
import { globals } from './eval/evalCode/globals'; | ||
import { WasmModuleConfig } from './types'; | ||
import { vmIntrinsics } from './utils'; | ||
|
@@ -37,23 +37,22 @@ export class QuickJsPlugin<State> implements WarpPlugin<QuickJsPluginInput, Prom | |
constructor(private readonly quickJsOptions: QuickJsOptions) {} | ||
|
||
async process(input: QuickJsPluginInput): Promise<QuickJsHandlerApi<State>> { | ||
({ | ||
QuickJS: this.QuickJS, | ||
runtime: this.runtime, | ||
vm: this.vm | ||
} = await this.configureWasmModule(input.binaryType)); | ||
({ QuickJS: this.QuickJS, runtime: this.runtime, vm: this.vm } = await this.configureWasmModule(input.binaryType)); | ||
this.setRuntimeOptions(); | ||
|
||
const quickJsEvaluator = new QuickJsEvaluator(this.vm); | ||
|
||
const isSourceAsync = input.contractSource.search('async') > -1; | ||
const processDecorator = isSourceAsync ? asyncDecorateProcessFn : decorateProcessFn; | ||
quickJsEvaluator.evalSeedRandom(); | ||
quickJsEvaluator.evalGlobalsCode(globals); | ||
quickJsEvaluator.evalHandleFnCode(decorateProcessFn, input.contractSource); | ||
quickJsEvaluator.evalHandleFnCode(processDecorator, input.contractSource); | ||
quickJsEvaluator.evalLogging(); | ||
quickJsEvaluator.evalPngJS(); | ||
quickJsEvaluator.evalRedStone(); | ||
quickJsEvaluator.evalExternal(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't |
||
quickJsEvaluator.dummyPromiseEval(); | ||
|
||
return new QuickJsHandlerApi(this.vm, this.runtime, this.QuickJS); | ||
return new QuickJsHandlerApi(this.vm, this.runtime, isSourceAsync); | ||
} | ||
|
||
setRuntimeOptions() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,7 @@ export const vmIntrinsics = { | |
...DefaultIntrinsics, | ||
Date: false, | ||
Proxy: false, | ||
Promise: false, | ||
Promise: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't we set this dynamically based on |
||
MapSet: false, | ||
BigFloat: false, | ||
BigInt: true, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?