From 948fa45c77c201dcaaacc260fa09ac06f75ef7dd Mon Sep 17 00:00:00 2001 From: robal Date: Fri, 3 Mar 2023 16:15:11 +0100 Subject: [PATCH] feat: introduce InteractionType for explicit marking of contract interactions as 'write' or 'view' This allows for some optimizations in WASM code: 'view' interactions do not require cloning of contract state gh-309 --- src/contract/HandlerBasedContract.ts | 37 +++++++++++++------ .../modules/impl/DefaultStateEvaluator.ts | 3 +- .../modules/impl/HandlerExecutorFactory.ts | 5 ++- src/core/modules/impl/handler/JsHandlerApi.ts | 5 ++- .../modules/impl/handler/WasmHandlerApi.ts | 21 ++++++----- .../modules/impl/wasm/rust-wasm-imports.ts | 13 ++++++- 6 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/contract/HandlerBasedContract.ts b/src/contract/HandlerBasedContract.ts index 75a229cb..a5096bea 100644 --- a/src/contract/HandlerBasedContract.ts +++ b/src/contract/HandlerBasedContract.ts @@ -6,7 +6,8 @@ import { ContractInteraction, HandlerApi, InteractionData, - InteractionResult + InteractionResult, + InteractionType } from '../core/modules/impl/HandlerExecutorFactory'; import { LexicographicalInteractionsSorter } from '../core/modules/impl/LexicographicalInteractionsSorter'; import { InteractionsSorter } from '../core/modules/InteractionsSorter'; @@ -212,7 +213,7 @@ export class HandlerBasedContract implements Contract { transfer: ArTransfer = emptyTransfer ): Promise> { this.logger.info('View state for', this._contractTxId); - return await this.callContract(input, undefined, undefined, tags, transfer); + return await this.callContract(input, 'view', undefined, undefined, tags, transfer); } async viewStateForTx( @@ -220,7 +221,7 @@ export class HandlerBasedContract implements Contract { interactionTx: GQLNodeInterface ): Promise> { this.logger.info(`View state for ${this._contractTxId}`); - return await this.doApplyInputOnTx(input, interactionTx); + return await this.doApplyInputOnTx(input, interactionTx, 'view'); } async dryWrite( @@ -230,12 +231,12 @@ export class HandlerBasedContract implements Contract { transfer?: ArTransfer ): Promise> { this.logger.info('Dry-write for', this._contractTxId); - return await this.callContract(input, caller, undefined, tags, transfer); + return await this.callContract(input, 'write', caller, undefined, tags, transfer); } async applyInput(input: Input, transaction: GQLNodeInterface): Promise> { this.logger.info(`Apply-input from transaction ${transaction.id} for ${this._contractTxId}`); - return await this.doApplyInputOnTx(input, transaction); + return await this.doApplyInputOnTx(input, transaction, 'write'); } async writeInteraction( @@ -370,7 +371,7 @@ export class HandlerBasedContract implements Contract { reward?: string ) { if (this._evaluationOptions.internalWrites) { - // it modifies tags + // it modifies tags await this.discoverInternalWrites(input, tags, transfer, strict, vrf); } @@ -400,7 +401,7 @@ export class HandlerBasedContract implements Contract { this.signature.type == 'arweave' ? await arweave.wallets.ownerToAddress(interactionTx.owner) : interactionTx.owner; - const handlerResult = await this.callContract(input, caller, undefined, tags, transfer, strict, vrf); + const handlerResult = await this.callContract(input, 'write', caller, undefined, tags, transfer, strict, vrf); if (handlerResult.type !== 'ok') { throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`); } @@ -630,6 +631,7 @@ export class HandlerBasedContract implements Contract { private async callContract( input: Input, + interactionType: InteractionType, caller?: string, sortKey?: string, tags: Tags = [], @@ -673,7 +675,8 @@ export class HandlerBasedContract implements Contract { // create interaction transaction const interaction: ContractInteraction = { input, - caller: executionContext.caller + caller: executionContext.caller, + interactionType }; this.logger.debug('interaction', interaction); @@ -723,7 +726,8 @@ export class HandlerBasedContract implements Contract { private async doApplyInputOnTx( input: Input, - interactionTx: GQLNodeInterface + interactionTx: GQLNodeInterface, + interactionType: InteractionType ): Promise> { this.maybeResetRootContract(); @@ -748,7 +752,8 @@ export class HandlerBasedContract implements Contract { const interaction: ContractInteraction = { input, - caller: this._parentContract.txId() + caller: this._parentContract.txId(), + interactionType }; const interactionData: InteractionData = { @@ -990,7 +995,17 @@ export class HandlerBasedContract implements Contract { strict: boolean, vrf: boolean ) { - const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict, vrf, false); + const handlerResult = await this.callContract( + input, + 'write', + undefined, + undefined, + tags, + transfer, + strict, + vrf, + false + ); if (strict && handlerResult.type !== 'ok') { throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`); diff --git a/src/core/modules/impl/DefaultStateEvaluator.ts b/src/core/modules/impl/DefaultStateEvaluator.ts index d972220e..914c36ea 100644 --- a/src/core/modules/impl/DefaultStateEvaluator.ts +++ b/src/core/modules/impl/DefaultStateEvaluator.ts @@ -213,7 +213,8 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { const interaction: ContractInteraction = { input, - caller: missingInteraction.owner.address + caller: missingInteraction.owner.address, + interactionType: 'write' }; const interactionData = { diff --git a/src/core/modules/impl/HandlerExecutorFactory.ts b/src/core/modules/impl/HandlerExecutorFactory.ts index 97b2acf5..d45328a4 100644 --- a/src/core/modules/impl/HandlerExecutorFactory.ts +++ b/src/core/modules/impl/HandlerExecutorFactory.ts @@ -243,7 +243,7 @@ async function getWasmModule(wasmResponse: Response, binary: Buffer): Promise { - interaction?: ContractInteraction; + interaction: ContractInteraction; interactionTx: GQLNodeInterface; } @@ -281,9 +281,12 @@ export type InteractionResult = HandlerResult & { originalErrorMessages?: Record; }; +export type InteractionType = 'view' | 'write'; + export type ContractInteraction = { input: Input; caller: string; + interactionType: InteractionType; }; export type InteractionResultType = 'ok' | 'error' | 'exception'; diff --git a/src/core/modules/impl/handler/JsHandlerApi.ts b/src/core/modules/impl/handler/JsHandlerApi.ts index 6827d89d..ecc38d02 100644 --- a/src/core/modules/impl/handler/JsHandlerApi.ts +++ b/src/core/modules/impl/handler/JsHandlerApi.ts @@ -52,9 +52,10 @@ export class JsHandlerApi extends AbstractContractHandler { executionContext: ExecutionContext ): Promise { if (this.contractDefinition.manifest?.evaluationOptions?.useConstructor) { - const interaction = { + const interaction: ContractInteraction = { input: { function: INIT_FUNC_NAME, args: initialState } as Input, - caller: this.contractDefinition.owner + caller: this.contractDefinition.owner, + interactionType: 'write' }; const interactionTx = { diff --git a/src/core/modules/impl/handler/WasmHandlerApi.ts b/src/core/modules/impl/handler/WasmHandlerApi.ts index 62c79b77..f2f55044 100644 --- a/src/core/modules/impl/handler/WasmHandlerApi.ts +++ b/src/core/modules/impl/handler/WasmHandlerApi.ts @@ -3,7 +3,7 @@ import { ContractDefinition } from '../../../../core/ContractDefinition'; import { ExecutionContext } from '../../../../core/ExecutionContext'; import { EvalStateResult } from '../../../../core/modules/StateEvaluator'; import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global'; -import { InteractionData, InteractionResult } from '../HandlerExecutorFactory'; +import { ContractInteraction, InteractionData, InteractionResult } from '../HandlerExecutorFactory'; import { AbstractContractHandler } from './AbstractContractHandler'; export class WasmHandlerApi extends AbstractContractHandler { @@ -89,17 +89,20 @@ export class WasmHandlerApi extends AbstractContractHandler { } } - private async doHandle(action: any): Promise { + private async doHandle(action: ContractInteraction): Promise { switch (this.contractDefinition.srcWasmLang) { case 'rust': { - let handleResult = await this.wasmExports.handle(action.input); - if (!handleResult) { - return; + + const handleResult = action.interactionType === 'write' ? await this.wasmExports.warpContractWrite(action.input) : await this.wasmExports.warpContractView(action.input); + + if (Object.prototype.hasOwnProperty.call(handleResult, 'WriteResponse')) { + return handleResult.WriteResponse; + } + if (Object.prototype.hasOwnProperty.call(handleResult, 'ViewResponse')) { + return handleResult.ViewResponse; } - if (Object.prototype.hasOwnProperty.call(handleResult, 'Ok')) { - return handleResult.Ok; - } else { - this.logger.debug('Error from rust', handleResult.Err); + { + this.logger.error('Error from rust', handleResult); let errorKey; let errorArgs = ''; if (typeof handleResult.Err === 'string' || handleResult.Err instanceof String) { diff --git a/src/core/modules/impl/wasm/rust-wasm-imports.ts b/src/core/modules/impl/wasm/rust-wasm-imports.ts index 1c110c4b..ba469fdf 100644 --- a/src/core/modules/impl/wasm/rust-wasm-imports.ts +++ b/src/core/modules/impl/wasm/rust-wasm-imports.ts @@ -585,11 +585,20 @@ export const rustWasmImports = (swGlobal, wbindgenImports, wasmInstance, dtorVal * @param {any} interaction * @returns {Promise} */ - module.handle = function (interaction) { - var ret = wasmInstance.exports.handle(addHeapObject(interaction)); + module.warpContractWrite = function (interaction) { + var ret = wasmInstance.exports.warpContractWrite(addHeapObject(interaction)); return takeObject(ret); }; + /** + * @param {any} interaction + * @returns {Promise} + */ + module.warpContractView = function (interaction) { + var ret = wasmInstance.exports.warpContractView(addHeapObject(interaction)); + return takeObject(ret); + }; + let stack_pointer = 32; function addBorrowedObject(obj) {