Skip to content

Commit

Permalink
feat: introduce InteractionType for explicit marking of contract inte…
Browse files Browse the repository at this point in the history
…ractions as 'write' or 'view'

This allows for some optimizations in WASM code: 'view' interactions do not require cloning of contract state

gh-309
  • Loading branch information
rpiszczatowski committed Mar 22, 2023
1 parent 554bbc5 commit 6cd1bf1
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 26 deletions.
37 changes: 26 additions & 11 deletions src/contract/HandlerBasedContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -212,15 +213,15 @@ export class HandlerBasedContract<State> implements Contract<State> {
transfer: ArTransfer = emptyTransfer
): Promise<InteractionResult<State, View>> {
this.logger.info('View state for', this._contractTxId);
return await this.callContract<Input, View>(input, undefined, undefined, tags, transfer);
return await this.callContract<Input, View>(input, 'view', undefined, undefined, tags, transfer);
}

async viewStateForTx<Input, View>(
input: Input,
interactionTx: GQLNodeInterface
): Promise<InteractionResult<State, View>> {
this.logger.info(`View state for ${this._contractTxId}`);
return await this.doApplyInputOnTx<Input, View>(input, interactionTx);
return await this.doApplyInputOnTx<Input, View>(input, interactionTx, 'view');
}

async dryWrite<Input>(
Expand All @@ -230,12 +231,12 @@ export class HandlerBasedContract<State> implements Contract<State> {
transfer?: ArTransfer
): Promise<InteractionResult<State, unknown>> {
this.logger.info('Dry-write for', this._contractTxId);
return await this.callContract<Input>(input, caller, undefined, tags, transfer);
return await this.callContract<Input>(input, 'write', caller, undefined, tags, transfer);
}

async applyInput<Input>(input: Input, transaction: GQLNodeInterface): Promise<InteractionResult<State, unknown>> {
this.logger.info(`Apply-input from transaction ${transaction.id} for ${this._contractTxId}`);
return await this.doApplyInputOnTx<Input>(input, transaction);
return await this.doApplyInputOnTx<Input>(input, transaction, 'write');
}

async writeInteraction<Input>(
Expand Down Expand Up @@ -370,7 +371,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
reward?: string
) {
if (this._evaluationOptions.internalWrites) {
// it modifies tags
// it modifies tags
await this.discoverInternalWrites<Input>(input, tags, transfer, strict, vrf);
}

Expand Down Expand Up @@ -400,7 +401,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
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}`);
}
Expand Down Expand Up @@ -630,6 +631,7 @@ export class HandlerBasedContract<State> implements Contract<State> {

private async callContract<Input, View = unknown>(
input: Input,
interactionType: InteractionType,
caller?: string,
sortKey?: string,
tags: Tags = [],
Expand Down Expand Up @@ -673,7 +675,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
// create interaction transaction
const interaction: ContractInteraction<Input> = {
input,
caller: executionContext.caller
caller: executionContext.caller,
interactionType
};

this.logger.debug('interaction', interaction);
Expand Down Expand Up @@ -723,7 +726,8 @@ export class HandlerBasedContract<State> implements Contract<State> {

private async doApplyInputOnTx<Input, View = unknown>(
input: Input,
interactionTx: GQLNodeInterface
interactionTx: GQLNodeInterface,
interactionType: InteractionType
): Promise<InteractionResult<State, View>> {
this.maybeResetRootContract();

Expand All @@ -748,7 +752,8 @@ export class HandlerBasedContract<State> implements Contract<State> {

const interaction: ContractInteraction<Input> = {
input,
caller: this._parentContract.txId()
caller: this._parentContract.txId(),
interactionType
};

const interactionData: InteractionData<Input> = {
Expand Down Expand Up @@ -990,7 +995,17 @@ export class HandlerBasedContract<State> implements Contract<State> {
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}`);
Expand Down
3 changes: 2 additions & 1 deletion src/core/modules/impl/DefaultStateEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {

const interaction: ContractInteraction<unknown> = {
input,
caller: missingInteraction.owner.address
caller: missingInteraction.owner.address,
interactionType: 'write'
};

const interactionData = {
Expand Down
5 changes: 4 additions & 1 deletion src/core/modules/impl/HandlerExecutorFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ async function getWasmModule(wasmResponse: Response, binary: Buffer): Promise<We
}

export interface InteractionData<Input> {
interaction?: ContractInteraction<Input>;
interaction: ContractInteraction<Input>;
interactionTx: GQLNodeInterface;
}

Expand Down Expand Up @@ -281,9 +281,12 @@ export type InteractionResult<State, Result> = HandlerResult<State, Result> & {
originalErrorMessages?: Record<string, string>;
};

export type InteractionType = 'view' | 'write';

export type ContractInteraction<Input> = {
input: Input;
caller: string;
interactionType: InteractionType;
};

export type InteractionResultType = 'ok' | 'error' | 'exception';
Expand Down
5 changes: 3 additions & 2 deletions src/core/modules/impl/handler/JsHandlerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
executionContext: ExecutionContext<State>
): Promise<State> {
if (this.contractDefinition.manifest?.evaluationOptions?.useConstructor) {
const interaction = {
const interaction: ContractInteraction<Input> = {
input: { function: INIT_FUNC_NAME, args: initialState } as Input,
caller: this.contractDefinition.owner
caller: this.contractDefinition.owner,
interactionType: 'write'
};

const interactionTx = {
Expand Down
21 changes: 12 additions & 9 deletions src/core/modules/impl/handler/WasmHandlerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<State> extends AbstractContractHandler<State> {
Expand Down Expand Up @@ -89,17 +89,20 @@ export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
}
}

private async doHandle(action: any): Promise<any> {
private async doHandle(action: ContractInteraction<unknown>): Promise<any> {
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) {
Expand Down
13 changes: 11 additions & 2 deletions src/core/modules/impl/wasm/rust-wasm-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,11 +585,20 @@ export const rustWasmImports = (swGlobal, wbindgenImports, wasmInstance, dtorVal
* @param {any} interaction
* @returns {Promise<any>}
*/
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<any>}
*/
module.warpContractView = function (interaction) {
var ret = wasmInstance.exports.warpContractView(addHeapObject(interaction));
return takeObject(ret);
};

let stack_pointer = 32;

function addBorrowedObject(obj) {
Expand Down

0 comments on commit 6cd1bf1

Please sign in to comment.